Projekt Dokumentation

Aufgabe: Vorhersage der Umsätze vom 9.6.2019 bis 30.07.2019

Infos zu den gegebenen Daten

Warengruppen: * 1 = Brot * 2 = Brötchen * 3 = Croissant * 4 = Konditorei * 5 = Kuchen * 6 = Saisonbrot

Saisonbrot muss nicht vorhergesagt werden! Siehe ‘predition_template.csv’.

Wetterdaten: * Mittlerer Bewölkungsgrad am Tag (0 = min, 8 = max) * MIttlere Temperatur in C * Mittlere Windgeschwindigkeit in m/s * Wettercode (http://www.seewetter-kiel.de/seewetter/daten_symbole.htm) * und in der Datei wettercodes.Rda

Vorbereitung & benötigte Libraries laden

remove(list = ls())
# Create list with needed libraries
pkgs <- c("lubridate", "stringr","tidyverse", "readr", 
          "fastDummies", "reticulate", "ggplot2", "Metrics", "VIM")

# Load each listed library and check if it is installed and install if necessary
for (pkg in pkgs) {
  if (!require(pkg, character.only = TRUE)) {
    install.packages(pkg)
    library(pkg, character.only = TRUE)
  }
}

Vorbereitete Datensätze laden

  • Wetterdaten wurden in “Datenaufbereitung_Wetter.Rmd” vorbereitet
  • Feiertagedaten wurden in “Datenaufbereitung_Feiertage.R” vorbereitet
  • Schulferien wurden in “Datenaufbereitung_Schulferien.R” vorbereitet
  • Umsatzdaten wurden in “Datenaufbereitung_Umsatz.R” vorbereitet
# Lade Daten
load("pj_wetter_dummy.Rda")
pj_wetter <- pj_wetter_dummy
  
load("kiwoDT.Rda")
pj_kiwo <- kiwoDT
  
load("pj_umsatz.Rda")

load("schulferien.Rda")
pj_schulferien <- schulferien

# Erste Betrachtung der Daten
#summary(pj_wetter)
#summary(pj_kiwo)
#summary(pj_umsatz)

allData_dummy

# Monatlichen Umsatzt von Nahrungsmittel Facheinzelhandel in SH (auch Bäckerein) --> Datenaufbereitung
umsatztFachEinzelHandelSH <- read_csv("umsatztFachEinzelHandel.csv")
New names:Rows: 73 Columns: 4── Column specification ──────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
dbl (4): ...1, Jahr, Monat, Umsatz
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
umsatztFachEinzelHandelSH <- select(umsatztFachEinzelHandelSH, "Jahr", "Monat", "Umsatz")
# Create a new column called "Datum"
umsatztFachEinzelHandelSH$Datum <- as.Date(paste(umsatztFachEinzelHandelSH$Jahr, umsatztFachEinzelHandelSH$Monat, "01", sep = "-"), format = "%Y-%m-%d")

# Remove the original Jahr and Monat columns
umsatztFachEinzelHandelSH$Jahr <- NULL
umsatztFachEinzelHandelSH$Monat <- NULL

#grouping the dataframe by year and month
umsatztFachEinzelHandelSH <- umsatztFachEinzelHandelSH %>% 
    group_by(year(Datum), month(Datum))
#selecting the first row of each group
first_row <- umsatztFachEinzelHandelSH %>% 
    slice_head(n=1)
#creating a new data frame with all the days in each month
days <- expand.grid(Jahr=unique(year(umsatztFachEinzelHandelSH$Datum)),Monat=unique(month(umsatztFachEinzelHandelSH$Datum)),Day=1:31)
# converting the above grid to a date format
days$Datum <- as.Date(paste(days$Jahr, days$Monat, days$Day, sep = "-"), format = "%Y-%m-%d")
# Removing the unnecessary columns from days dataframe
days$Jahr<-NULL
days$Monat<-NULL
days$Day<-NULL
#merging the two dataframe
umsatztFachEinzelHandelSH<-left_join(days,first_row,by=c("Datum"))
# Remove the original Jahr and Monat columns
umsatztFachEinzelHandelSH$`year(Datum)` <- NULL
umsatztFachEinzelHandelSH$`month(Datum)` <- NULL
umsatztFachEinzelHandelSH <- umsatztFachEinzelHandelSH %>% 
    filter(Datum >= as.Date("2013-07-01"))
umsatztFachEinzelHandelSH <- umsatztFachEinzelHandelSH %>% 
    filter(Datum < as.Date("2019-07-31"))

umsatztFachEinzelHandelSH <- umsatztFachEinzelHandelSH %>%  
  hotdeck(variable = c("Umsatz"), ord_var = "Datum")

ggplot(umsatztFachEinzelHandelSH) +
  geom_point(aes(x = Datum, y = Umsatz, color = Umsatz_imp))


umsatztFachEinzelHandelSH$Umsatz_imp <- NULL

# Merge erstellt automatisch die Schnittmenge
# Der Zusatz all.x = TRUE sorgt dafür, dass keine Zeilen (basierend auf Datensatz x) weggelöscht werden
# Wetterdaten nach Datum hinzufügen
pj_umsatz_wetter <- merge(pj_umsatz, pj_wetter, by="Datum", all.x = TRUE)

# Schulferien nach Datum hinzufügen
pj_umsatz_wetter_ferien <- merge(pj_umsatz_wetter, pj_schulferien, by="Datum", all.x = TRUE)

# KiWo nach Datum hinzufügen
allData <- merge(pj_umsatz_wetter_ferien, pj_kiwo, by="Datum", all.x = TRUE)

allData <- merge(allData, umsatztFachEinzelHandelSH, by="Datum", all.x = TRUE)

# auf fehlende Werte überprüfen:
allData_na <- allData %>%
  aggr(combined=TRUE, numbers=TRUE)
Warning: not enough horizontal space to display frequencies

# Imputation Temperatur und Windstaerke
# Aktuell: "Datenspende" vom Wert vom Vortag
# ZIEL: Mittelwert aus Temperatur von Vortag und Tag danach -> Armando! :)
allData <- allData %>%  
  hotdeck(variable = c("Temperatur", "Windstaerke"),
          ord_var = "Datum")

#imputierte Werte graphisch überprüfen:
ggplot(allData) +
  geom_point(aes(x = Datum, y = Temperatur, color = Temperatur_imp))

ggplot(allData) +
  geom_point(aes(x = Datum, y = Windstaerke, color = Windstaerke_imp))


# NA Wettercodes zu 0, da Spalte WC_NA angibt, wo Wettercodes gefehlt haben
# Spalten 12 -24

# das gleiche gilt bei der Bewölkung
# Spalten 26 - 29

# weitere NA mit 0 füllen, dort wo es Sinn ergibt  

allData <- allData %>%
    mutate_at(c(12:34), ~replace(., is.na(.), 0))

# dummy coding der Wochentage
allData_dummy <- dummy_cols(allData, select_columns = "Wochentag")

allData_dummy$year <- year(allData_dummy$Datum)
allData_dummy$month <- month(allData_dummy$Datum)
allData_dummy$day <- day(allData_dummy$Datum)

allData_dummy$Datum <- NULL

summary(allData_dummy)
      Brot           Brötchen        Croissant        Konditorei         Kuchen         Saisonbrot    
 Min.   : 23.11   Min.   : 175.0   Min.   : 37.74   Min.   : 27.43   Min.   : 121.5   Min.   :  0.00  
 1st Qu.: 98.14   1st Qu.: 286.8   1st Qu.:106.79   1st Qu.: 66.12   1st Qu.: 231.1   1st Qu.:  0.00  
 Median :121.89   Median : 367.8   Median :143.90   Median : 79.67   Median : 268.2   Median :  0.00  
 Mean   :124.40   Mean   : 398.9   Mean   :164.52   Mean   : 87.12   Mean   : 278.2   Mean   : 11.02  
 3rd Qu.:147.06   3rd Qu.: 486.1   3rd Qu.:205.06   3rd Qu.: 97.41   3rd Qu.: 309.1   3rd Qu.:  0.00  
 Max.   :416.79   Max.   :1203.4   Max.   :565.94   Max.   :430.50   Max.   :1879.5   Max.   :172.87  
  Wochentag         Konditorei_imp   Windstaerke      Temperatur     WC_Bewölkung_abnehmend
 Length:2123        Mode :logical   Min.   : 2.00   Min.   :-8.475   Min.   :0             
 Class :character   FALSE:2069      1st Qu.: 5.00   1st Qu.: 6.312   1st Qu.:0             
 Mode  :character   TRUE :54        Median : 5.00   Median :11.650   Median :0             
                                    Mean   : 5.58   Mean   :12.031   Mean   :0             
                                    3rd Qu.: 6.00   3rd Qu.:17.769   3rd Qu.:0             
                                    Max.   :12.00   Max.   :32.671   Max.   :0             
 WC_Bewölkung_gleichbleibend WC_Bewölkung_nicht_beobachtet WC_Bewölkung_zunehmend WC_Dunst_Staub   
 Min.   :0                   Min.   :0.00000               Min.   :0.000000       Min.   :0.00000  
 1st Qu.:0                   1st Qu.:0.00000               1st Qu.:0.000000       1st Qu.:0.00000  
 Median :0                   Median :0.00000               Median :0.000000       Median :0.00000  
 Mean   :0                   Mean   :0.09279               Mean   :0.000471       Mean   :0.06924  
 3rd Qu.:0                   3rd Qu.:0.00000               3rd Qu.:0.000000       3rd Qu.:0.00000  
 Max.   :0                   Max.   :1.00000               Max.   :1.000000       Max.   :1.00000  
 WC_Ereignisse_letzte_h  WC_Gewitter      WC_Nebel_Eisnebel    WC_Regen        WC_Schauer   WC_Schnee      
 Min.   :0.0000         Min.   :0.00000   Min.   :0.00000   Min.   :0.0000   Min.   :0    Min.   :0.00000  
 1st Qu.:0.0000         1st Qu.:0.00000   1st Qu.:0.00000   1st Qu.:0.0000   1st Qu.:0    1st Qu.:0.00000  
 Median :0.0000         Median :0.00000   Median :0.00000   Median :0.0000   Median :0    Median :0.00000  
 Mean   :0.1512         Mean   :0.01319   Mean   :0.01413   Mean   :0.3151   Mean   :0    Mean   :0.01978  
 3rd Qu.:0.0000         3rd Qu.:0.00000   3rd Qu.:0.00000   3rd Qu.:1.0000   3rd Qu.:0    3rd Qu.:0.00000  
 Max.   :1.0000         Max.   :1.00000   Max.   :1.00000   Max.   :1.0000   Max.   :0    Max.   :1.00000  
 WC_Sprühregen      WC_Trockenereignisse     WC_NA        Bewoelkungsgrad_gering Bewoelkungsgrad_keine
 Min.   :0.000000   Min.   :0.00000      Min.   :0.0000   Min.   :0.0000         Min.   :0.00         
 1st Qu.:0.000000   1st Qu.:0.00000      1st Qu.:0.0000   1st Qu.:0.0000         1st Qu.:0.00         
 Median :0.000000   Median :0.00000      Median :0.0000   Median :0.0000         Median :0.00         
 Mean   :0.004239   Mean   :0.07772      Mean   :0.2346   Mean   :0.1182         Mean   :0.13         
 3rd Qu.:0.000000   3rd Qu.:0.00000      3rd Qu.:0.0000   3rd Qu.:0.0000         3rd Qu.:0.00         
 Max.   :1.000000   Max.   :1.00000      Max.   :1.0000   Max.   :1.0000         Max.   :1.00         
 Bewoelkungsgrad_mittel Bewoelkungsgrad_stark Bewoelkungsgrad_NA  Schulferien      KielerWoche    
 Min.   :0.0000         Min.   :0.0000        Min.   :0.000000   Min.   :0.0000   Min.   :0.0000  
 1st Qu.:0.0000         1st Qu.:0.0000        1st Qu.:0.000000   1st Qu.:0.0000   1st Qu.:0.0000  
 Median :0.0000         Median :0.0000        Median :0.000000   Median :0.0000   Median :0.0000  
 Mean   :0.3627         Mean   :0.3773        Mean   :0.004239   Mean   :0.2398   Mean   :0.0212  
 3rd Qu.:1.0000         3rd Qu.:1.0000        3rd Qu.:0.000000   3rd Qu.:0.0000   3rd Qu.:0.0000  
 Max.   :1.0000         Max.   :1.0000        Max.   :1.000000   Max.   :1.0000   Max.   :1.0000  
     Umsatz      Temperatur_imp     Windstaerke_imp Wochentag_Friday Wochentag_Monday Wochentag_Saturday
 Min.   : 68.2   Min.   :0.000000   Mode :logical   Min.   :0.0000   Min.   :0.0000   Min.   :0.0000    
 1st Qu.:104.8   1st Qu.:0.000000   FALSE:2107      1st Qu.:0.0000   1st Qu.:0.0000   1st Qu.:0.0000    
 Median :120.4   Median :0.000000   TRUE :16        Median :0.0000   Median :0.0000   Median :0.0000    
 Mean   :117.1   Mean   :0.007536                   Mean   :0.1413   Mean   :0.1423   Mean   :0.1441    
 3rd Qu.:129.4   3rd Qu.:0.000000                   3rd Qu.:0.0000   3rd Qu.:0.0000   3rd Qu.:0.0000    
 Max.   :155.2   Max.   :1.000000                   Max.   :1.0000   Max.   :1.0000   Max.   :1.0000    
 Wochentag_Sunday Wochentag_Thursday Wochentag_Tuesday Wochentag_Wednesday      year          month      
 Min.   :0.0000   Min.   :0.0000     Min.   :0.0000    Min.   :0.0000      Min.   :2013   Min.   : 1.00  
 1st Qu.:0.0000   1st Qu.:0.0000     1st Qu.:0.0000    1st Qu.:0.0000      1st Qu.:2014   1st Qu.: 3.50  
 Median :0.0000   Median :0.0000     Median :0.0000    Median :0.0000      Median :2016   Median : 7.00  
 Mean   :0.1432   Mean   :0.1432     Mean   :0.1432    Mean   :0.1427      Mean   :2016   Mean   : 6.52  
 3rd Qu.:0.0000   3rd Qu.:0.0000     3rd Qu.:0.0000    3rd Qu.:0.0000      3rd Qu.:2017   3rd Qu.:10.00  
 Max.   :1.0000   Max.   :1.0000     Max.   :1.0000    Max.   :1.0000      Max.   :2019   Max.   :12.00  
      day      
 Min.   : 1.0  
 1st Qu.: 8.0  
 Median :16.0  
 Mean   :15.7  
 3rd Qu.:23.0  
 Max.   :31.0  
save(allData_dummy, file="projectData_dummy.Rda")

Testdatensatz

# Erstelle einen leeren Dataframe mit einer Spalte für das Datum
testDatenSatz <- data.frame(Datum = character())

# Erstelle eine Sequenz von Daten im angegebenen Zeitraum
datum_sequenz <- seq(from = as.Date("2019-06-09"),
                     to = as.Date("2019-07-30"),
                     by = "days")

# Füge die Daten der Sequenz dem Dataframe hinzu
sBrot <- select(pj_umsatz, "Datum", "Saisonbrot")
testDatenSatz <- rbind(testDatenSatz, data.frame(Datum = datum_sequenz))
testDatenSatz$Wochentag <- weekdays(testDatenSatz$Datum)
testDatenSatz <- merge(testDatenSatz, pj_wetter, by="Datum", all.x = TRUE)
testDatenSatz <- merge(testDatenSatz, pj_schulferien, by="Datum", all.x = TRUE)
testDatenSatz <- merge(testDatenSatz, pj_kiwo, by="Datum", all.x = TRUE)
testDatenSatz <- merge(testDatenSatz, sBrot, by="Datum", all.x = TRUE)
testDatenSatz <- merge(testDatenSatz, umsatztFachEinzelHandelSH, by="Datum", all.x = TRUE)

testDatenSatz <- testDatenSatz %>% 
  hotdeck(variable = c("Temperatur", "Windstaerke"),
          ord_var = "Datum")

#imputierte Werte von testDatenSatz graphisch überprüfen:
ggplot(testDatenSatz) +
  geom_point(aes(x = Datum, y = Temperatur, color = Temperatur_imp))

ggplot(testDatenSatz) +
  geom_point(aes(x = Datum, y = Windstaerke, color = Windstaerke_imp))


testDatenSatz <- testDatenSatz %>%
    mutate_at(c(4:26), ~replace(., is.na(.), 0))

# dummy coding der Wochentage
testDatenSatz <- dummy_cols(testDatenSatz, select_columns = "Wochentag")

testDatenSatz$year <- year(testDatenSatz$Datum)
testDatenSatz$month <- month(testDatenSatz$Datum)
testDatenSatz$day <- day(testDatenSatz$Datum)

testDatenSatz$Datum <- NULL

summary(testDatenSatz)
  Wochentag          Windstaerke      Temperatur    WC_Bewölkung_abnehmend WC_Bewölkung_gleichbleibend
 Length:52          Min.   :3.000   Min.   :14.46   Min.   :0              Min.   :0                  
 Class :character   1st Qu.:5.000   1st Qu.:16.93   1st Qu.:0              1st Qu.:0                  
 Mode  :character   Median :6.000   Median :19.59   Median :0              Median :0                  
                    Mean   :5.788   Mean   :20.41   Mean   :0              Mean   :0                  
                    3rd Qu.:7.000   3rd Qu.:23.40   3rd Qu.:0              3rd Qu.:0                  
                    Max.   :9.000   Max.   :29.73   Max.   :0              Max.   :0                  
 WC_Bewölkung_nicht_beobachtet WC_Bewölkung_zunehmend WC_Dunst_Staub   WC_Ereignisse_letzte_h  WC_Gewitter    
 Min.   :0.0000                Min.   :0              Min.   :0.0000   Min.   :0.0000         Min.   :0.0000  
 1st Qu.:0.0000                1st Qu.:0              1st Qu.:0.0000   1st Qu.:0.0000         1st Qu.:0.0000  
 Median :0.0000                Median :0              Median :0.0000   Median :0.0000         Median :0.0000  
 Mean   :0.1538                Mean   :0              Mean   :0.1346   Mean   :0.1538         Mean   :0.1154  
 3rd Qu.:0.0000                3rd Qu.:0              3rd Qu.:0.0000   3rd Qu.:0.0000         3rd Qu.:0.0000  
 Max.   :1.0000                Max.   :0              Max.   :1.0000   Max.   :1.0000         Max.   :1.0000  
 WC_Nebel_Eisnebel    WC_Regen        WC_Schauer   WC_Schnee WC_Sprühregen     WC_Trockenereignisse
 Min.   :0         Min.   :0.0000   Min.   :0    Min.   :0   Min.   :0.00000   Min.   :0.00000     
 1st Qu.:0         1st Qu.:0.0000   1st Qu.:0    1st Qu.:0   1st Qu.:0.00000   1st Qu.:0.00000     
 Median :0         Median :0.0000   Median :0    Median :0   Median :0.00000   Median :0.00000     
 Mean   :0         Mean   :0.1731   Mean   :0    Mean   :0   Mean   :0.01923   Mean   :0.01923     
 3rd Qu.:0         3rd Qu.:0.0000   3rd Qu.:0    3rd Qu.:0   3rd Qu.:0.00000   3rd Qu.:0.00000     
 Max.   :0         Max.   :1.0000   Max.   :0    Max.   :0   Max.   :1.00000   Max.   :1.00000     
     WC_NA        Bewoelkungsgrad_gering Bewoelkungsgrad_keine Bewoelkungsgrad_mittel Bewoelkungsgrad_stark
 Min.   :0.0000   Min.   :0.00000        Min.   :0.0000        Min.   :0.0000         Min.   :0.0000       
 1st Qu.:0.0000   1st Qu.:0.00000        1st Qu.:0.0000        1st Qu.:0.0000         1st Qu.:0.0000       
 Median :0.0000   Median :0.00000        Median :0.0000        Median :0.0000         Median :0.0000       
 Mean   :0.2308   Mean   :0.05769        Mean   :0.1346        Mean   :0.4615         Mean   :0.3462       
 3rd Qu.:0.0000   3rd Qu.:0.00000        3rd Qu.:0.0000        3rd Qu.:1.0000         3rd Qu.:1.0000       
 Max.   :1.0000   Max.   :1.00000        Max.   :1.0000        Max.   :1.0000         Max.   :1.0000       
 Bewoelkungsgrad_NA  Schulferien      KielerWoche       Saisonbrot     Umsatz      Temperatur_imp 
 Min.   :0          Min.   :0.0000   Min.   :0.0000   Min.   :0    Min.   :118.5   Mode :logical  
 1st Qu.:0          1st Qu.:0.0000   1st Qu.:0.0000   1st Qu.:0    1st Qu.:118.5   FALSE:52       
 Median :0          Median :1.0000   Median :0.0000   Median :0    Median :122.4                  
 Mean   :0          Mean   :0.5769   Mean   :0.1731   Mean   :0    Mean   :120.8                  
 3rd Qu.:0          3rd Qu.:1.0000   3rd Qu.:0.0000   3rd Qu.:0    3rd Qu.:122.4                  
 Max.   :0          Max.   :1.0000   Max.   :1.0000   Max.   :0    Max.   :122.4                  
 Windstaerke_imp Wochentag_Friday Wochentag_Monday Wochentag_Saturday Wochentag_Sunday Wochentag_Thursday
 Mode :logical   Min.   :0.0000   Min.   :0.0000   Min.   :0.0000     Min.   :0.0000   Min.   :0.0000    
 FALSE:52        1st Qu.:0.0000   1st Qu.:0.0000   1st Qu.:0.0000     1st Qu.:0.0000   1st Qu.:0.0000    
                 Median :0.0000   Median :0.0000   Median :0.0000     Median :0.0000   Median :0.0000    
                 Mean   :0.1346   Mean   :0.1538   Mean   :0.1346     Mean   :0.1538   Mean   :0.1346    
                 3rd Qu.:0.0000   3rd Qu.:0.0000   3rd Qu.:0.0000     3rd Qu.:0.0000   3rd Qu.:0.0000    
                 Max.   :1.0000   Max.   :1.0000   Max.   :1.0000     Max.   :1.0000   Max.   :1.0000    
 Wochentag_Tuesday Wochentag_Wednesday      year          month            day       
 Min.   :0.0000    Min.   :0.0000      Min.   :2019   Min.   :6.000   Min.   : 1.00  
 1st Qu.:0.0000    1st Qu.:0.0000      1st Qu.:2019   1st Qu.:6.000   1st Qu.:11.00  
 Median :0.0000    Median :0.0000      Median :2019   Median :7.000   Median :17.50  
 Mean   :0.1538    Mean   :0.1346      Mean   :2019   Mean   :6.577   Mean   :17.19  
 3rd Qu.:0.0000    3rd Qu.:0.0000      3rd Qu.:2019   3rd Qu.:7.000   3rd Qu.:24.00  
 Max.   :1.0000    Max.   :1.0000      Max.   :2019   Max.   :7.000   Max.   :30.00  
save(testDatenSatz, file="Datenaufbereitung_Testdaten.Rda")

Features & Labels

features <- c("day",                           "month",                         "year",
              "Windstaerke",                   "Temperatur",                    "WC_Bewölkung_abnehmend",
              "WC_Bewölkung_gleichbleibend",   "WC_Bewölkung_nicht_beobachtet", "WC_Bewölkung_zunehmend",
              "WC_Dunst_Staub",                "WC_Ereignisse_letzte_h",        "WC_Gewitter",
              "WC_Nebel_Eisnebel",             "WC_Regen",                      "WC_Schauer",
              "WC_Schnee",                     "WC_Sprühregen",                 "WC_Trockenereignisse",
              "WC_NA",                         "Bewoelkungsgrad_gering",        "Bewoelkungsgrad_keine",
              "Bewoelkungsgrad_mittel",        "Bewoelkungsgrad_stark",         "Bewoelkungsgrad_NA",
              "Schulferien",                   "KielerWoche",                   "Wochentag_Tuesday",          
              "Wochentag_Thursday",            "Saisonbrot",                    "Umsatz",
              "Wochentag_Friday",              "Wochentag_Wednesday",           "Wochentag_Monday",
              "Wochentag_Saturday",            "Wochentag_Sunday")

labels <- c("Brot", "Brötchen", "Croissant", "Konditorei", "Kuchen")

Selection of Training, Validation and Test Data

# Setting the random counter to a fixed value, so the random initialization stays the same (the random split is always the same)
set.seed(1)

assignment <- sample(1:2, size = nrow(allData_dummy), prob = c(.8, .2), replace = TRUE)

# Create training, validation and test data for the features and the labels
training_features <- allData_dummy[assignment == 1, features]    
training_labels <- allData_dummy[assignment == 1, labels]    
training_labels <- as.data.frame(training_labels)

validation_features <- allData_dummy[assignment == 2, features]  
validation_labels <- allData_dummy[assignment == 2, labels]  
validation_labels <- as.data.frame(validation_labels)

testing_features <- testDatenSatz %>% select(all_of(features))

#are there any missing values?
table(is.na(training_features))

FALSE 
59150 
table(is.na(validation_features))

FALSE 
15155 
table(is.na(testing_features))

FALSE 
 1820 
#summary(allData_dummy)

Modell aufstellen in Python

reticulate::repl_python()
import numpy as np
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import InputLayer, Dense, BatchNormalization, Dropout
from tensorflow.keras.optimizers import RMSprop

# The argument "input_shape" for the definition of the input layer must include 
# the number of input variables (features) used for the model. 
# To automatically calculate this number we use the function `r.training_features.keys()`, 
# which returns the list of variable names of the dataframe `training_features`.
# Then, the funtion `len()` returns the length of this list of variable names 
# (i.e. the number of variables in the input)

model = Sequential([
  InputLayer(input_shape = (len(r.training_features.keys()), )),
  BatchNormalization(),
  Dense(len(r.training_features.keys()), activation = 'swish'),
  Dropout(0.3),
  Dense(len(r.training_features.keys()), activation = 'swish'),
  Dropout(0.3),
  Dense(len(r.training_features.keys()), activation = 'swish'),
  Dropout(0.3),
  Dense(len(r.training_features.keys()), activation = 'swish'),
  Dense(5)
])

# Ausgabe einer ZUsammenfassung zur Form des MOdells, das geschätzt wird (nicht notwendig)
#model.summary()

Schätzung de neuronalen Netzes

# definition of the loss function and the optimazation function with hyperparameters
model.compile(loss="mape", optimizer=RMSprop(learning_rate=0.001))

#Schätzung des Modells
history = model.fit(r.training_features, r.training_labels, epochs = 400,
                    validation_data = (r.validation_features, r.validation_labels), verbose = 0)
                    
model.save("python_model.h5")

graphische Ausgabe der Modelloptimierung

quit
# Graphische Ausgabe der Modelloptimierung

#create data
data <- data.frame(val_loss = unlist(py$history$history$val_loss),
                   loss = unlist(py$history$history$loss))

ggplot(data[-(1:10), ])+
  geom_line(aes(x = 1:length(val_loss), y = val_loss, colour = "Validation Loss")) +
  geom_line(aes(x = 1:length(loss), y = loss, colour = "Training Loss")) +
  scale_colour_manual(values = c("Training Loss"="blue", "Validation Loss" = "red")) +
  labs(title = "Loss Function Values During Optimazation") +
  xlab("Iteration Number") +
  ylab("Loss")

Auswertung der Schätzergebnisse

# Schätzung der (normierten) Preise für die Trainings- und Testdaten
training_predictions <- py$model$predict(training_features)

 1/53 [..............................] - ETA: 1s
53/53 [==============================] - 0s 344us/step
validation_predictions <- py$model$predict(validation_features)

 1/14 [=>............................] - ETA: 0s
14/14 [==============================] - 0s 342us/step
testing_predictions <- py$model$predict(testing_features)

1/2 [==============>...............] - ETA: 0s
2/2 [==============================] - 0s 414us/step
# Vergleich der Gütekriterien für die Traingings- und Testdaten
a <- format(mape(training_labels[,1], training_predictions[,1])*100, digits=3, nsmall=2)
b <- format(mape(training_labels[,2], training_predictions[,2])*100, digits=3, nsmall=2)
c <- format(mape(training_labels[,3], training_predictions[,3])*100, digits=3, nsmall=2)
d <- format(mape(training_labels[,4], training_predictions[,4])*100, digits=3, nsmall=2)
e <- format(mape(training_labels[,5], training_predictions[,5])*100, digits=3, nsmall=2)

cat(paste0("\nMAPE on the Training Data1:\t", a))

MAPE on the Training Data1: 18.36
cat(paste0("\nMAPE on the Training Data2:\t", b))

MAPE on the Training Data2: 11.86
cat(paste0("\nMAPE on the Training Data3:\t", c))

MAPE on the Training Data3: 17.67
cat(paste0("\nMAPE on the Training Data4:\t", d))

MAPE on the Training Data4: 20.15
cat(paste0("\nMAPE on the Training Data5:\t", e, "\n"))

MAPE on the Training Data5: 13.61
g <- format(mape(validation_labels[,1], validation_predictions[,1])*100, digits=3, nsmall=2)
h <- format(mape(validation_labels[,2], validation_predictions[,2])*100, digits=3, nsmall=2)
i <- format(mape(validation_labels[,3], validation_predictions[,3])*100, digits=3, nsmall=2)
j <- format(mape(validation_labels[,4], validation_predictions[,4])*100, digits=3, nsmall=2)
k <- format(mape(validation_labels[,5], validation_predictions[,5])*100, digits=3, nsmall=2)
  
cat(paste0("\nMAPE on the Validation Data1:\t", g))

MAPE on the Validation Data1:   19.63
cat(paste0("\nMAPE on the Validation Data2:\t", h))

MAPE on the Validation Data2:   13.53
cat(paste0("\nMAPE on the Validation Data3:\t", i))

MAPE on the Validation Data3:   20.68
cat(paste0("\nMAPE on the Validation Data4:\t", j))

MAPE on the Validation Data4:   20.13
cat(paste0("\nMAPE on the Validation Data5:\t", k, "\n"))

MAPE on the Validation Data5:   14.81
# Mean of Training and Validation Data MAPE
meanT <- c(as.double(a), as.double(b), as.double(c), as.double(d), as.double(e)) 
meanV <- c(as.double(g), as.double(h), as.double(i), as.double(j), as.double(k)) 

cat(paste0("\nMean Training MAPE: ", mean(meanT), "\n"))

Mean Training MAPE: 16.33
cat(paste0("Mean Validation MAPE: ", mean(meanV), "\n"))
Mean Validation MAPE: 17.756

Grafischer vergleich der vorhergesagten & tatsächlicher Preise für die Trainings- und Validierungsdaten

data_train <- data.frame(prediction = training_predictions[,1], actual = training_labels[,1])
data_val <- data.frame(prediction = validation_predictions[,1], actual = validation_labels[,1])
data_test <- data.frame(prediction = testing_predictions[,1])

# Plot der Ergebnisse der Trainingsdaten
ggplot(data_train[]) +
  geom_line( aes(x=1:length(prediction), y=prediction, colour = "Predicted Values" )) +
  geom_line( aes(x=1:length(actual), y=actual, colour = "Actual Values" )) +
  scale_colour_manual( values = c("Predicted Values"="blue", "Actual Values"="red") ) +
  labs(title="Predicted and Actual Values for the Training Data 1") +
  xlab("Case Number") +
  ylab("Price in EUR") 


# Plot der Ergebnisse der Validierungsdaten
ggplot(data_val[,]) +
  geom_line( aes(x=1:length(prediction), y=prediction, colour = "Predicted Values" )) +
  geom_line( aes(x=1:length(actual), y=actual, colour = "Actual Values" )) +
  scale_colour_manual( values = c("Predicted Values"="blue", "Actual Values"="red") ) +
  labs(title="Predicted and Actual Values for the Validation Data 1") +
  xlab("Case Number") +
  ylab("Price in EUR")


# Plot der Ergebnisse der Testdaten
ggplot(data_test) +
  geom_line( aes(x=1:length(prediction), y=prediction, colour = "Predicted Values" )) +
  labs(title="Prediction for the Test Data 1") +
  xlab("Case Number") +
  ylab("Price in EUR") 


#------------------------- 2 -------------------------#

data_train2 <- data.frame(prediction = training_predictions[,2], actual = training_labels[,2])
data_val2 <- data.frame(prediction = validation_predictions[,2], actual = validation_labels[,2])
data_test2 <- data.frame(prediction = testing_predictions[,2])

# Plot der Ergebnisse der Trainingsdaten
ggplot(data_train2) +
  geom_line( aes(x=1:length(prediction), y=prediction, colour = "Predicted Values" )) +
  geom_line( aes(x=1:length(actual), y=actual, colour = "Actual Values" )) +
  scale_colour_manual( values = c("Predicted Values"="blue", "Actual Values"="red") ) +
  labs(title="Predicted and Actual Values for the Training Data 2") +
  xlab("Case Number") +
  ylab("Price in EUR") 


# Plot der Ergebnisse der Validierungsdaten
ggplot(data_val2) +
  geom_line( aes(x=1:length(prediction), y=prediction, colour = "Predicted Values" )) +
  geom_line( aes(x=1:length(actual), y=actual, colour = "Actual Values" )) +
  scale_colour_manual( values = c("Predicted Values"="blue", "Actual Values"="red") ) +
  labs(title="Predicted and Actual Values for the Validation Data 2") +
  xlab("Case Number") +
  ylab("Price in EUR")


# Plot der Ergebnisse der Testdaten
ggplot(data_test2) +
  geom_line( aes(x=1:length(prediction), y=prediction, colour = "Predicted Values" )) +
  labs(title="Prediction for the Test Data 2") +
  xlab("Case Number") +
  ylab("Price in EUR") 


#------------------------- 3 -------------------------#

data_train3 <- data.frame(prediction = training_predictions[,3], actual = training_labels[,3])
data_val3 <- data.frame(prediction = validation_predictions[,3], actual = validation_labels[,3])
data_test3 <- data.frame(prediction = testing_predictions[,3])

# Plot der Ergebnisse der Trainingsdaten
ggplot(data_train3) +
  geom_line( aes(x=1:length(prediction), y=prediction, colour = "Predicted Values" )) +
  geom_line( aes(x=1:length(actual), y=actual, colour = "Actual Values" )) +
  scale_colour_manual( values = c("Predicted Values"="blue", "Actual Values"="red") ) +
  labs(title="Predicted and Actual Values for the Training Data 3") +
  xlab("Case Number") +
  ylab("Price in EUR") 


# Plot der Ergebnisse der Validierungsdaten
ggplot(data_val3) +
  geom_line( aes(x=1:length(prediction), y=prediction, colour = "Predicted Values" )) +
  geom_line( aes(x=1:length(actual), y=actual, colour = "Actual Values" )) +
  scale_colour_manual( values = c("Predicted Values"="blue", "Actual Values"="red") ) +
  labs(title="Predicted and Actual Values for the Validation Data 3") +
  xlab("Case Number") +
  ylab("Price in EUR")


# Plot der Ergebnisse der Testdaten
ggplot(data_test3) +
  geom_line( aes(x=1:length(prediction), y=prediction, colour = "Predicted Values" )) +
  labs(title="Prediction for the Test Data 3") +
  xlab("Case Number") +
  ylab("Price in EUR") 



#------------------------- 4 -------------------------#

data_train4 <- data.frame(prediction = training_predictions[,4], actual = training_labels[,4])
data_val4 <- data.frame(prediction = validation_predictions[,4], actual = validation_labels[,4])
data_test4 <- data.frame(prediction = testing_predictions[,4])

# Plot der Ergebnisse der Trainingsdaten
ggplot(data_train4) +
  geom_line( aes(x=1:length(prediction), y=prediction, colour = "Predicted Values" )) +
  geom_line( aes(x=1:length(actual), y=actual, colour = "Actual Values" )) +
  scale_colour_manual( values = c("Predicted Values"="blue", "Actual Values"="red") ) +
  labs(title="Predicted and Actual Values for the Training Data 4") +
  xlab("Case Number") +
  ylab("Price in EUR") 


# Plot der Ergebnisse der Validierungsdaten
ggplot(data_val4) +
  geom_line( aes(x=1:length(prediction), y=prediction, colour = "Predicted Values" )) +
  geom_line( aes(x=1:length(actual), y=actual, colour = "Actual Values" )) +
  scale_colour_manual( values = c("Predicted Values"="blue", "Actual Values"="red") ) +
  labs(title="Predicted and Actual Values for the Validation Data 4") +
  xlab("Case Number") +
  ylab("Price in EUR")


# Plot der Ergebnisse der Testdaten
ggplot(data_test4) +
  geom_line( aes(x=1:length(prediction), y=prediction, colour = "Predicted Values" )) +
  labs(title="Prediction for the Test Data 4") +
  xlab("Case Number") +
  ylab("Price in EUR") 


#------------------------- 5 -------------------------#

data_train5 <- data.frame(prediction = training_predictions[,5], actual = training_labels[,5])
data_val5 <- data.frame(prediction = validation_predictions[,5], actual = validation_labels[,5])
data_test5 <- data.frame(prediction = testing_predictions[,5])

# Plot der Ergebnisse der Trainingsdaten
ggplot(data_train5) +
  geom_line( aes(x=1:length(prediction), y=prediction, colour = "Predicted Values" )) +
  geom_line( aes(x=1:length(actual), y=actual, colour = "Actual Values" )) +
  scale_colour_manual( values = c("Predicted Values"="blue", "Actual Values"="red") ) +
  labs(title="Predicted and Actual Values for the Training Data 5") +
  xlab("Case Number") +
  ylab("Price in EUR") 


# Plot der Ergebnisse der Validierungsdaten
ggplot(data_val5) +
  geom_line( aes(x=1:length(prediction), y=prediction, colour = "Predicted Values" )) +
  geom_line( aes(x=1:length(actual), y=actual, colour = "Actual Values" )) +
  scale_colour_manual( values = c("Predicted Values"="blue", "Actual Values"="red") ) +
  labs(title="Predicted and Actual Values for the Validation Data 5") +
  xlab("Case Number") +
  ylab("Price in EUR")


# Plot der Ergebnisse der Testdaten
ggplot(data_test5) +
  geom_line( aes(x=1:length(prediction), y=prediction, colour = "Predicted Values" )) +
  labs(title="Prediction for the Test Data 5") +
  xlab("Case Number") +
  ylab("Price in EUR") 

LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCmVkaXRvcl9vcHRpb25zOiAKICBtYXJrZG93bjogCiAgICB3cmFwOiA3MgotLS0KCiMgUHJvamVrdCBEb2t1bWVudGF0aW9uCgpBdWZnYWJlOiBWb3JoZXJzYWdlIGRlciBVbXPDpHR6ZSB2b20gOS42LjIwMTkgYmlzIDMwLjA3LjIwMTkKCiMjIyBJbmZvcyB6dSBkZW4gZ2VnZWJlbmVuIERhdGVuCgpXYXJlbmdydXBwZW46IFwqIDEgPSBCcm90IFwqIDIgPSBCcsO2dGNoZW4gXCogMyA9IENyb2lzc2FudCBcKiA0ID0KS29uZGl0b3JlaSBcKiA1ID0gS3VjaGVuIFwqIDYgPSBTYWlzb25icm90CgojIyMgU2Fpc29uYnJvdCBtdXNzIG5pY2h0IHZvcmhlcmdlc2FndCB3ZXJkZW4hIFNpZWhlICdwcmVkaXRpb25fdGVtcGxhdGUuY3N2Jy4KCldldHRlcmRhdGVuOiBcKiBNaXR0bGVyZXIgQmV3w7Zsa3VuZ3NncmFkIGFtIFRhZyAoMCA9IG1pbiwgOCA9IG1heCkgXCoKTUl0dGxlcmUgVGVtcGVyYXR1ciBpbiBDIFwqIE1pdHRsZXJlIFdpbmRnZXNjaHdpbmRpZ2tlaXQgaW4gbS9zIFwqCldldHRlcmNvZGUgKDxodHRwOi8vd3d3LnNlZXdldHRlci1raWVsLmRlL3NlZXdldHRlci9kYXRlbl9zeW1ib2xlLmh0bT4pClwqIHVuZCBpbiBkZXIgRGF0ZWkgd2V0dGVyY29kZXMuUmRhCgojIyMgVm9yYmVyZWl0dW5nICYgYmVuw7Z0aWd0ZSBMaWJyYXJpZXMgbGFkZW4KCmBgYHtyfQpyZW1vdmUobGlzdCA9IGxzKCkpCiMgQ3JlYXRlIGxpc3Qgd2l0aCBuZWVkZWQgbGlicmFyaWVzCnBrZ3MgPC0gYygibHVicmlkYXRlIiwgInN0cmluZ3IiLCJ0aWR5dmVyc2UiLCAicmVhZHIiLCAKICAgICAgICAgICJmYXN0RHVtbWllcyIsICJyZXRpY3VsYXRlIiwgImdncGxvdDIiLCAiTWV0cmljcyIsICJWSU0iKQoKIyBMb2FkIGVhY2ggbGlzdGVkIGxpYnJhcnkgYW5kIGNoZWNrIGlmIGl0IGlzIGluc3RhbGxlZCBhbmQgaW5zdGFsbCBpZiBuZWNlc3NhcnkKZm9yIChwa2cgaW4gcGtncykgewogIGlmICghcmVxdWlyZShwa2csIGNoYXJhY3Rlci5vbmx5ID0gVFJVRSkpIHsKICAgIGluc3RhbGwucGFja2FnZXMocGtnKQogICAgbGlicmFyeShwa2csIGNoYXJhY3Rlci5vbmx5ID0gVFJVRSkKICB9Cn0KYGBgCgojIyMgVm9yYmVyZWl0ZXRlIERhdGVuc8OkdHplIGxhZGVuCgotICAgV2V0dGVyZGF0ZW4gd3VyZGVuIGluICJEYXRlbmF1ZmJlcmVpdHVuZ19XZXR0ZXIuUm1kIiB2b3JiZXJlaXRldAotICAgRmVpZXJ0YWdlZGF0ZW4gd3VyZGVuIGluICJEYXRlbmF1ZmJlcmVpdHVuZ19GZWllcnRhZ2UuUiIgdm9yYmVyZWl0ZXQKLSAgIFNjaHVsZmVyaWVuIHd1cmRlbiBpbiAiRGF0ZW5hdWZiZXJlaXR1bmdfU2NodWxmZXJpZW4uUiIgdm9yYmVyZWl0ZXQKLSAgIFVtc2F0emRhdGVuIHd1cmRlbiBpbiAiRGF0ZW5hdWZiZXJlaXR1bmdfVW1zYXR6LlIiIHZvcmJlcmVpdGV0CgpgYGB7cn0KIyBMYWRlIERhdGVuCmxvYWQoInBqX3dldHRlcl9kdW1teS5SZGEiKQpwal93ZXR0ZXIgPC0gcGpfd2V0dGVyX2R1bW15CiAgCmxvYWQoImtpd29EVC5SZGEiKQpwal9raXdvIDwtIGtpd29EVAogIApsb2FkKCJwal91bXNhdHouUmRhIikKCmxvYWQoInNjaHVsZmVyaWVuLlJkYSIpCnBqX3NjaHVsZmVyaWVuIDwtIHNjaHVsZmVyaWVuCgojIEVyc3RlIEJldHJhY2h0dW5nIGRlciBEYXRlbgojc3VtbWFyeShwal93ZXR0ZXIpCiNzdW1tYXJ5KHBqX2tpd28pCiNzdW1tYXJ5KHBqX3Vtc2F0eikKYGBgCgojIyMgYWxsRGF0YV9kdW1teQoKYGBge3J9CiMgTW9uYXRsaWNoZW4gVW1zYXR6dCB2b24gTmFocnVuZ3NtaXR0ZWwgRmFjaGVpbnplbGhhbmRlbCBpbiBTSCAoYXVjaCBCw6Rja2VyZWluKSAtLT4gRGF0ZW5hdWZiZXJlaXR1bmcKdW1zYXR6dEZhY2hFaW56ZWxIYW5kZWxTSCA8LSByZWFkX2NzdigidW1zYXR6dEZhY2hFaW56ZWxIYW5kZWwuY3N2IikKdW1zYXR6dEZhY2hFaW56ZWxIYW5kZWxTSCA8LSBzZWxlY3QodW1zYXR6dEZhY2hFaW56ZWxIYW5kZWxTSCwgIkphaHIiLCAiTW9uYXQiLCAiVW1zYXR6IikKIyBDcmVhdGUgYSBuZXcgY29sdW1uIGNhbGxlZCAiRGF0dW0iCnVtc2F0enRGYWNoRWluemVsSGFuZGVsU0gkRGF0dW0gPC0gYXMuRGF0ZShwYXN0ZSh1bXNhdHp0RmFjaEVpbnplbEhhbmRlbFNIJEphaHIsIHVtc2F0enRGYWNoRWluemVsSGFuZGVsU0gkTW9uYXQsICIwMSIsIHNlcCA9ICItIiksIGZvcm1hdCA9ICIlWS0lbS0lZCIpCgojIFJlbW92ZSB0aGUgb3JpZ2luYWwgSmFociBhbmQgTW9uYXQgY29sdW1ucwp1bXNhdHp0RmFjaEVpbnplbEhhbmRlbFNIJEphaHIgPC0gTlVMTAp1bXNhdHp0RmFjaEVpbnplbEhhbmRlbFNIJE1vbmF0IDwtIE5VTEwKCiNncm91cGluZyB0aGUgZGF0YWZyYW1lIGJ5IHllYXIgYW5kIG1vbnRoCnVtc2F0enRGYWNoRWluemVsSGFuZGVsU0ggPC0gdW1zYXR6dEZhY2hFaW56ZWxIYW5kZWxTSCAlPiUgCiAgICBncm91cF9ieSh5ZWFyKERhdHVtKSwgbW9udGgoRGF0dW0pKQojc2VsZWN0aW5nIHRoZSBmaXJzdCByb3cgb2YgZWFjaCBncm91cApmaXJzdF9yb3cgPC0gdW1zYXR6dEZhY2hFaW56ZWxIYW5kZWxTSCAlPiUgCiAgICBzbGljZV9oZWFkKG49MSkKI2NyZWF0aW5nIGEgbmV3IGRhdGEgZnJhbWUgd2l0aCBhbGwgdGhlIGRheXMgaW4gZWFjaCBtb250aApkYXlzIDwtIGV4cGFuZC5ncmlkKEphaHI9dW5pcXVlKHllYXIodW1zYXR6dEZhY2hFaW56ZWxIYW5kZWxTSCREYXR1bSkpLE1vbmF0PXVuaXF1ZShtb250aCh1bXNhdHp0RmFjaEVpbnplbEhhbmRlbFNIJERhdHVtKSksRGF5PTE6MzEpCiMgY29udmVydGluZyB0aGUgYWJvdmUgZ3JpZCB0byBhIGRhdGUgZm9ybWF0CmRheXMkRGF0dW0gPC0gYXMuRGF0ZShwYXN0ZShkYXlzJEphaHIsIGRheXMkTW9uYXQsIGRheXMkRGF5LCBzZXAgPSAiLSIpLCBmb3JtYXQgPSAiJVktJW0tJWQiKQojIFJlbW92aW5nIHRoZSB1bm5lY2Vzc2FyeSBjb2x1bW5zIGZyb20gZGF5cyBkYXRhZnJhbWUKZGF5cyRKYWhyPC1OVUxMCmRheXMkTW9uYXQ8LU5VTEwKZGF5cyREYXk8LU5VTEwKI21lcmdpbmcgdGhlIHR3byBkYXRhZnJhbWUKdW1zYXR6dEZhY2hFaW56ZWxIYW5kZWxTSDwtbGVmdF9qb2luKGRheXMsZmlyc3Rfcm93LGJ5PWMoIkRhdHVtIikpCiMgUmVtb3ZlIHRoZSBvcmlnaW5hbCBKYWhyIGFuZCBNb25hdCBjb2x1bW5zCnVtc2F0enRGYWNoRWluemVsSGFuZGVsU0gkYHllYXIoRGF0dW0pYCA8LSBOVUxMCnVtc2F0enRGYWNoRWluemVsSGFuZGVsU0gkYG1vbnRoKERhdHVtKWAgPC0gTlVMTAp1bXNhdHp0RmFjaEVpbnplbEhhbmRlbFNIIDwtIHVtc2F0enRGYWNoRWluemVsSGFuZGVsU0ggJT4lIAogICAgZmlsdGVyKERhdHVtID49IGFzLkRhdGUoIjIwMTMtMDctMDEiKSkKdW1zYXR6dEZhY2hFaW56ZWxIYW5kZWxTSCA8LSB1bXNhdHp0RmFjaEVpbnplbEhhbmRlbFNIICU+JSAKICAgIGZpbHRlcihEYXR1bSA8IGFzLkRhdGUoIjIwMTktMDctMzEiKSkKCnVtc2F0enRGYWNoRWluemVsSGFuZGVsU0ggPC0gdW1zYXR6dEZhY2hFaW56ZWxIYW5kZWxTSCAlPiUgIAogIGhvdGRlY2sodmFyaWFibGUgPSBjKCJVbXNhdHoiKSwgb3JkX3ZhciA9ICJEYXR1bSIpCgpnZ3Bsb3QodW1zYXR6dEZhY2hFaW56ZWxIYW5kZWxTSCkgKwogIGdlb21fcG9pbnQoYWVzKHggPSBEYXR1bSwgeSA9IFVtc2F0eiwgY29sb3IgPSBVbXNhdHpfaW1wKSkKCnVtc2F0enRGYWNoRWluemVsSGFuZGVsU0gkVW1zYXR6X2ltcCA8LSBOVUxMCgojIE1lcmdlIGVyc3RlbGx0IGF1dG9tYXRpc2NoIGRpZSBTY2huaXR0bWVuZ2UKIyBEZXIgWnVzYXR6IGFsbC54ID0gVFJVRSBzb3JndCBkYWbDvHIsIGRhc3Mga2VpbmUgWmVpbGVuIChiYXNpZXJlbmQgYXVmIERhdGVuc2F0eiB4KSB3ZWdnZWzDtnNjaHQgd2VyZGVuCiMgV2V0dGVyZGF0ZW4gbmFjaCBEYXR1bSBoaW56dWbDvGdlbgpwal91bXNhdHpfd2V0dGVyIDwtIG1lcmdlKHBqX3Vtc2F0eiwgcGpfd2V0dGVyLCBieT0iRGF0dW0iLCBhbGwueCA9IFRSVUUpCgojIFNjaHVsZmVyaWVuIG5hY2ggRGF0dW0gaGluenVmw7xnZW4KcGpfdW1zYXR6X3dldHRlcl9mZXJpZW4gPC0gbWVyZ2UocGpfdW1zYXR6X3dldHRlciwgcGpfc2NodWxmZXJpZW4sIGJ5PSJEYXR1bSIsIGFsbC54ID0gVFJVRSkKCiMgS2lXbyBuYWNoIERhdHVtIGhpbnp1ZsO8Z2VuCmFsbERhdGEgPC0gbWVyZ2UocGpfdW1zYXR6X3dldHRlcl9mZXJpZW4sIHBqX2tpd28sIGJ5PSJEYXR1bSIsIGFsbC54ID0gVFJVRSkKCmFsbERhdGEgPC0gbWVyZ2UoYWxsRGF0YSwgdW1zYXR6dEZhY2hFaW56ZWxIYW5kZWxTSCwgYnk9IkRhdHVtIiwgYWxsLnggPSBUUlVFKQoKIyBhdWYgZmVobGVuZGUgV2VydGUgw7xiZXJwcsO8ZmVuOgphbGxEYXRhX25hIDwtIGFsbERhdGEgJT4lCiAgYWdncihjb21iaW5lZD1UUlVFLCBudW1iZXJzPVRSVUUpCgojIEltcHV0YXRpb24gVGVtcGVyYXR1ciB1bmQgV2luZHN0YWVya2UKIyBBa3R1ZWxsOiAiRGF0ZW5zcGVuZGUiIHZvbSBXZXJ0IHZvbSBWb3J0YWcKIyBaSUVMOiBNaXR0ZWx3ZXJ0IGF1cyBUZW1wZXJhdHVyIHZvbiBWb3J0YWcgdW5kIFRhZyBkYW5hY2ggLT4gQXJtYW5kbyEgOikKYWxsRGF0YSA8LSBhbGxEYXRhICU+JSAgCiAgaG90ZGVjayh2YXJpYWJsZSA9IGMoIlRlbXBlcmF0dXIiLCAiV2luZHN0YWVya2UiKSwKICAgICAgICAgIG9yZF92YXIgPSAiRGF0dW0iKQoKI2ltcHV0aWVydGUgV2VydGUgZ3JhcGhpc2NoIMO8YmVycHLDvGZlbjoKZ2dwbG90KGFsbERhdGEpICsKICBnZW9tX3BvaW50KGFlcyh4ID0gRGF0dW0sIHkgPSBUZW1wZXJhdHVyLCBjb2xvciA9IFRlbXBlcmF0dXJfaW1wKSkKZ2dwbG90KGFsbERhdGEpICsKICBnZW9tX3BvaW50KGFlcyh4ID0gRGF0dW0sIHkgPSBXaW5kc3RhZXJrZSwgY29sb3IgPSBXaW5kc3RhZXJrZV9pbXApKQoKIyBOQSBXZXR0ZXJjb2RlcyB6dSAwLCBkYSBTcGFsdGUgV0NfTkEgYW5naWJ0LCB3byBXZXR0ZXJjb2RlcyBnZWZlaGx0IGhhYmVuCiMgU3BhbHRlbiAxMiAtMjQKCiMgZGFzIGdsZWljaGUgZ2lsdCBiZWkgZGVyIEJld8O2bGt1bmcKIyBTcGFsdGVuIDI2IC0gMjkKCiMgd2VpdGVyZSBOQSBtaXQgMCBmw7xsbGVuLCBkb3J0IHdvIGVzIFNpbm4gZXJnaWJ0ICAKCmFsbERhdGEgPC0gYWxsRGF0YSAlPiUKICAgIG11dGF0ZV9hdChjKDEyOjM0KSwgfnJlcGxhY2UoLiwgaXMubmEoLiksIDApKQoKIyBkdW1teSBjb2RpbmcgZGVyIFdvY2hlbnRhZ2UKYWxsRGF0YV9kdW1teSA8LSBkdW1teV9jb2xzKGFsbERhdGEsIHNlbGVjdF9jb2x1bW5zID0gIldvY2hlbnRhZyIpCgphbGxEYXRhX2R1bW15JHllYXIgPC0geWVhcihhbGxEYXRhX2R1bW15JERhdHVtKQphbGxEYXRhX2R1bW15JG1vbnRoIDwtIG1vbnRoKGFsbERhdGFfZHVtbXkkRGF0dW0pCmFsbERhdGFfZHVtbXkkZGF5IDwtIGRheShhbGxEYXRhX2R1bW15JERhdHVtKQoKYWxsRGF0YV9kdW1teSREYXR1bSA8LSBOVUxMCgpzdW1tYXJ5KGFsbERhdGFfZHVtbXkpCnNhdmUoYWxsRGF0YV9kdW1teSwgZmlsZT0icHJvamVjdERhdGFfZHVtbXkuUmRhIikKYGBgCgojIyMgVGVzdGRhdGVuc2F0egoKYGBge3J9CiMgRXJzdGVsbGUgZWluZW4gbGVlcmVuIERhdGFmcmFtZSBtaXQgZWluZXIgU3BhbHRlIGbDvHIgZGFzIERhdHVtCnRlc3REYXRlblNhdHogPC0gZGF0YS5mcmFtZShEYXR1bSA9IGNoYXJhY3RlcigpKQoKIyBFcnN0ZWxsZSBlaW5lIFNlcXVlbnogdm9uIERhdGVuIGltIGFuZ2VnZWJlbmVuIFplaXRyYXVtCmRhdHVtX3NlcXVlbnogPC0gc2VxKGZyb20gPSBhcy5EYXRlKCIyMDE5LTA2LTA5IiksCiAgICAgICAgICAgICAgICAgICAgIHRvID0gYXMuRGF0ZSgiMjAxOS0wNy0zMCIpLAogICAgICAgICAgICAgICAgICAgICBieSA9ICJkYXlzIikKCiMgRsO8Z2UgZGllIERhdGVuIGRlciBTZXF1ZW56IGRlbSBEYXRhZnJhbWUgaGluenUKc0Jyb3QgPC0gc2VsZWN0KHBqX3Vtc2F0eiwgIkRhdHVtIiwgIlNhaXNvbmJyb3QiKQp0ZXN0RGF0ZW5TYXR6IDwtIHJiaW5kKHRlc3REYXRlblNhdHosIGRhdGEuZnJhbWUoRGF0dW0gPSBkYXR1bV9zZXF1ZW56KSkKdGVzdERhdGVuU2F0eiRXb2NoZW50YWcgPC0gd2Vla2RheXModGVzdERhdGVuU2F0eiREYXR1bSkKdGVzdERhdGVuU2F0eiA8LSBtZXJnZSh0ZXN0RGF0ZW5TYXR6LCBwal93ZXR0ZXIsIGJ5PSJEYXR1bSIsIGFsbC54ID0gVFJVRSkKdGVzdERhdGVuU2F0eiA8LSBtZXJnZSh0ZXN0RGF0ZW5TYXR6LCBwal9zY2h1bGZlcmllbiwgYnk9IkRhdHVtIiwgYWxsLnggPSBUUlVFKQp0ZXN0RGF0ZW5TYXR6IDwtIG1lcmdlKHRlc3REYXRlblNhdHosIHBqX2tpd28sIGJ5PSJEYXR1bSIsIGFsbC54ID0gVFJVRSkKdGVzdERhdGVuU2F0eiA8LSBtZXJnZSh0ZXN0RGF0ZW5TYXR6LCBzQnJvdCwgYnk9IkRhdHVtIiwgYWxsLnggPSBUUlVFKQp0ZXN0RGF0ZW5TYXR6IDwtIG1lcmdlKHRlc3REYXRlblNhdHosIHVtc2F0enRGYWNoRWluemVsSGFuZGVsU0gsIGJ5PSJEYXR1bSIsIGFsbC54ID0gVFJVRSkKCnRlc3REYXRlblNhdHogPC0gdGVzdERhdGVuU2F0eiAlPiUgCiAgaG90ZGVjayh2YXJpYWJsZSA9IGMoIlRlbXBlcmF0dXIiLCAiV2luZHN0YWVya2UiKSwKICAgICAgICAgIG9yZF92YXIgPSAiRGF0dW0iKQoKI2ltcHV0aWVydGUgV2VydGUgdm9uIHRlc3REYXRlblNhdHogZ3JhcGhpc2NoIMO8YmVycHLDvGZlbjoKZ2dwbG90KHRlc3REYXRlblNhdHopICsKICBnZW9tX3BvaW50KGFlcyh4ID0gRGF0dW0sIHkgPSBUZW1wZXJhdHVyLCBjb2xvciA9IFRlbXBlcmF0dXJfaW1wKSkKZ2dwbG90KHRlc3REYXRlblNhdHopICsKICBnZW9tX3BvaW50KGFlcyh4ID0gRGF0dW0sIHkgPSBXaW5kc3RhZXJrZSwgY29sb3IgPSBXaW5kc3RhZXJrZV9pbXApKQoKdGVzdERhdGVuU2F0eiA8LSB0ZXN0RGF0ZW5TYXR6ICU+JQogICAgbXV0YXRlX2F0KGMoNDoyNiksIH5yZXBsYWNlKC4sIGlzLm5hKC4pLCAwKSkKCiMgZHVtbXkgY29kaW5nIGRlciBXb2NoZW50YWdlCnRlc3REYXRlblNhdHogPC0gZHVtbXlfY29scyh0ZXN0RGF0ZW5TYXR6LCBzZWxlY3RfY29sdW1ucyA9ICJXb2NoZW50YWciKQoKdGVzdERhdGVuU2F0eiR5ZWFyIDwtIHllYXIodGVzdERhdGVuU2F0eiREYXR1bSkKdGVzdERhdGVuU2F0eiRtb250aCA8LSBtb250aCh0ZXN0RGF0ZW5TYXR6JERhdHVtKQp0ZXN0RGF0ZW5TYXR6JGRheSA8LSBkYXkodGVzdERhdGVuU2F0eiREYXR1bSkKCnRlc3REYXRlblNhdHokRGF0dW0gPC0gTlVMTAoKc3VtbWFyeSh0ZXN0RGF0ZW5TYXR6KQpzYXZlKHRlc3REYXRlblNhdHosIGZpbGU9IkRhdGVuYXVmYmVyZWl0dW5nX1Rlc3RkYXRlbi5SZGEiKQpgYGAKCiMjIyBGZWF0dXJlcyAmIExhYmVscwoKYGBge3J9CmZlYXR1cmVzIDwtIGMoImRheSIsICAgICAgICAgICAgICAgICAgICAgICAgICAgIm1vbnRoIiwgICAgICAgICAgICAgICAgICAgICAgICAgInllYXIiLAogICAgICAgICAgICAgICJXaW5kc3RhZXJrZSIsICAgICAgICAgICAgICAgICAgICJUZW1wZXJhdHVyIiwgICAgICAgICAgICAgICAgICAgICJXQ19CZXfDtmxrdW5nX2FibmVobWVuZCIsCiAgICAgICAgICAgICAgIldDX0Jld8O2bGt1bmdfZ2xlaWNoYmxlaWJlbmQiLCAgICJXQ19CZXfDtmxrdW5nX25pY2h0X2Jlb2JhY2h0ZXQiLCAiV0NfQmV3w7Zsa3VuZ196dW5laG1lbmQiLAogICAgICAgICAgICAgICJXQ19EdW5zdF9TdGF1YiIsICAgICAgICAgICAgICAgICJXQ19FcmVpZ25pc3NlX2xldHp0ZV9oIiwgICAgICAgICJXQ19HZXdpdHRlciIsCiAgICAgICAgICAgICAgIldDX05lYmVsX0Vpc25lYmVsIiwgICAgICAgICAgICAgIldDX1JlZ2VuIiwgICAgICAgICAgICAgICAgICAgICAgIldDX1NjaGF1ZXIiLAogICAgICAgICAgICAgICJXQ19TY2huZWUiLCAgICAgICAgICAgICAgICAgICAgICJXQ19TcHLDvGhyZWdlbiIsICAgICAgICAgICAgICAgICAiV0NfVHJvY2tlbmVyZWlnbmlzc2UiLAogICAgICAgICAgICAgICJXQ19OQSIsICAgICAgICAgICAgICAgICAgICAgICAgICJCZXdvZWxrdW5nc2dyYWRfZ2VyaW5nIiwgICAgICAgICJCZXdvZWxrdW5nc2dyYWRfa2VpbmUiLAogICAgICAgICAgICAgICJCZXdvZWxrdW5nc2dyYWRfbWl0dGVsIiwgICAgICAgICJCZXdvZWxrdW5nc2dyYWRfc3RhcmsiLCAgICAgICAgICJCZXdvZWxrdW5nc2dyYWRfTkEiLAogICAgICAgICAgICAgICJTY2h1bGZlcmllbiIsICAgICAgICAgICAgICAgICAgICJLaWVsZXJXb2NoZSIsICAgICAgICAgICAgICAgICAgICJXb2NoZW50YWdfVHVlc2RheSIsICAgICAgICAgIAogICAgICAgICAgICAgICJXb2NoZW50YWdfVGh1cnNkYXkiLCAgICAgICAgICAgICJTYWlzb25icm90IiwgICAgICAgICAgICAgICAgICAgICJVbXNhdHoiLAogICAgICAgICAgICAgICJXb2NoZW50YWdfRnJpZGF5IiwgICAgICAgICAgICAgICJXb2NoZW50YWdfV2VkbmVzZGF5IiwgICAgICAgICAgICJXb2NoZW50YWdfTW9uZGF5IiwKICAgICAgICAgICAgICAiV29jaGVudGFnX1NhdHVyZGF5IiwgICAgICAgICAgICAiV29jaGVudGFnX1N1bmRheSIpCgpsYWJlbHMgPC0gYygiQnJvdCIsICJCcsO2dGNoZW4iLCAiQ3JvaXNzYW50IiwgIktvbmRpdG9yZWkiLCAiS3VjaGVuIikKYGBgCgojIyMgU2VsZWN0aW9uIG9mIFRyYWluaW5nLCBWYWxpZGF0aW9uIGFuZCBUZXN0IERhdGEKCmBgYHtyfQojIFNldHRpbmcgdGhlIHJhbmRvbSBjb3VudGVyIHRvIGEgZml4ZWQgdmFsdWUsIHNvIHRoZSByYW5kb20gaW5pdGlhbGl6YXRpb24gc3RheXMgdGhlIHNhbWUgKHRoZSByYW5kb20gc3BsaXQgaXMgYWx3YXlzIHRoZSBzYW1lKQpzZXQuc2VlZCgxKQoKYXNzaWdubWVudCA8LSBzYW1wbGUoMToyLCBzaXplID0gbnJvdyhhbGxEYXRhX2R1bW15KSwgcHJvYiA9IGMoLjgsIC4yKSwgcmVwbGFjZSA9IFRSVUUpCgojIENyZWF0ZSB0cmFpbmluZywgdmFsaWRhdGlvbiBhbmQgdGVzdCBkYXRhIGZvciB0aGUgZmVhdHVyZXMgYW5kIHRoZSBsYWJlbHMKdHJhaW5pbmdfZmVhdHVyZXMgPC0gYWxsRGF0YV9kdW1teVthc3NpZ25tZW50ID09IDEsIGZlYXR1cmVzXSAgICAKdHJhaW5pbmdfbGFiZWxzIDwtIGFsbERhdGFfZHVtbXlbYXNzaWdubWVudCA9PSAxLCBsYWJlbHNdICAgIAp0cmFpbmluZ19sYWJlbHMgPC0gYXMuZGF0YS5mcmFtZSh0cmFpbmluZ19sYWJlbHMpCgp2YWxpZGF0aW9uX2ZlYXR1cmVzIDwtIGFsbERhdGFfZHVtbXlbYXNzaWdubWVudCA9PSAyLCBmZWF0dXJlc10gIAp2YWxpZGF0aW9uX2xhYmVscyA8LSBhbGxEYXRhX2R1bW15W2Fzc2lnbm1lbnQgPT0gMiwgbGFiZWxzXSAgCnZhbGlkYXRpb25fbGFiZWxzIDwtIGFzLmRhdGEuZnJhbWUodmFsaWRhdGlvbl9sYWJlbHMpCgp0ZXN0aW5nX2ZlYXR1cmVzIDwtIHRlc3REYXRlblNhdHogJT4lIHNlbGVjdChhbGxfb2YoZmVhdHVyZXMpKQoKI2FyZSB0aGVyZSBhbnkgbWlzc2luZyB2YWx1ZXM/CnRhYmxlKGlzLm5hKHRyYWluaW5nX2ZlYXR1cmVzKSkKdGFibGUoaXMubmEodmFsaWRhdGlvbl9mZWF0dXJlcykpCnRhYmxlKGlzLm5hKHRlc3RpbmdfZmVhdHVyZXMpKQojc3VtbWFyeShhbGxEYXRhX2R1bW15KQpgYGAKCiMjIyBNb2RlbGwgYXVmc3RlbGxlbiBpbiBQeXRob24KCmBgYHtweXRob259CmltcG9ydCBudW1weSBhcyBucAppbXBvcnQgdGVuc29yZmxvdyBhcyB0Zgpmcm9tIHRlbnNvcmZsb3cua2VyYXMubW9kZWxzIGltcG9ydCBTZXF1ZW50aWFsCmZyb20gdGVuc29yZmxvdy5rZXJhcy5sYXllcnMgaW1wb3J0IElucHV0TGF5ZXIsIERlbnNlLCBCYXRjaE5vcm1hbGl6YXRpb24sIERyb3BvdXQKZnJvbSB0ZW5zb3JmbG93LmtlcmFzLm9wdGltaXplcnMgaW1wb3J0IFJNU3Byb3AKCiMgVGhlIGFyZ3VtZW50ICJpbnB1dF9zaGFwZSIgZm9yIHRoZSBkZWZpbml0aW9uIG9mIHRoZSBpbnB1dCBsYXllciBtdXN0IGluY2x1ZGUgCiMgdGhlIG51bWJlciBvZiBpbnB1dCB2YXJpYWJsZXMgKGZlYXR1cmVzKSB1c2VkIGZvciB0aGUgbW9kZWwuIAojIFRvIGF1dG9tYXRpY2FsbHkgY2FsY3VsYXRlIHRoaXMgbnVtYmVyIHdlIHVzZSB0aGUgZnVuY3Rpb24gYHIudHJhaW5pbmdfZmVhdHVyZXMua2V5cygpYCwgCiMgd2hpY2ggcmV0dXJucyB0aGUgbGlzdCBvZiB2YXJpYWJsZSBuYW1lcyBvZiB0aGUgZGF0YWZyYW1lIGB0cmFpbmluZ19mZWF0dXJlc2AuCiMgVGhlbiwgdGhlIGZ1bnRpb24gYGxlbigpYCByZXR1cm5zIHRoZSBsZW5ndGggb2YgdGhpcyBsaXN0IG9mIHZhcmlhYmxlIG5hbWVzIAojIChpLmUuIHRoZSBudW1iZXIgb2YgdmFyaWFibGVzIGluIHRoZSBpbnB1dCkKCm1vZGVsID0gU2VxdWVudGlhbChbCiAgSW5wdXRMYXllcihpbnB1dF9zaGFwZSA9IChsZW4oci50cmFpbmluZ19mZWF0dXJlcy5rZXlzKCkpLCApKSwKICBCYXRjaE5vcm1hbGl6YXRpb24oKSwKICBEZW5zZShsZW4oci50cmFpbmluZ19mZWF0dXJlcy5rZXlzKCkpLCBhY3RpdmF0aW9uID0gJ3N3aXNoJyksCiAgRHJvcG91dCgwLjIpLAogIERlbnNlKGxlbihyLnRyYWluaW5nX2ZlYXR1cmVzLmtleXMoKSksIGFjdGl2YXRpb24gPSAnc3dpc2gnKSwKICBEcm9wb3V0KDAuMiksCiAgRGVuc2UobGVuKHIudHJhaW5pbmdfZmVhdHVyZXMua2V5cygpKSwgYWN0aXZhdGlvbiA9ICdzd2lzaCcpLAogIERyb3BvdXQoMC4yKSwKICBEZW5zZShsZW4oci50cmFpbmluZ19mZWF0dXJlcy5rZXlzKCkpLCBhY3RpdmF0aW9uID0gJ3N3aXNoJyksCiAgRGVuc2UoNSkKXSkKCiMgQXVzZ2FiZSBlaW5lciBaVXNhbW1lbmZhc3N1bmcgenVyIEZvcm0gZGVzIE1PZGVsbHMsIGRhcyBnZXNjaMOkdHp0IHdpcmQgKG5pY2h0IG5vdHdlbmRpZykKI21vZGVsLnN1bW1hcnkoKQpgYGAKCiMjIyBTY2jDpHR6dW5nIGRlIG5ldXJvbmFsZW4gTmV0emVzCgpgYGB7cHl0aG9ufQojIGRlZmluaXRpb24gb2YgdGhlIGxvc3MgZnVuY3Rpb24gYW5kIHRoZSBvcHRpbWF6YXRpb24gZnVuY3Rpb24gd2l0aCBoeXBlcnBhcmFtZXRlcnMKbW9kZWwuY29tcGlsZShsb3NzPSJtYXBlIiwgb3B0aW1pemVyPVJNU3Byb3AobGVhcm5pbmdfcmF0ZT0wLjAwMSkpCgojU2Now6R0enVuZyBkZXMgTW9kZWxscwpoaXN0b3J5ID0gbW9kZWwuZml0KHIudHJhaW5pbmdfZmVhdHVyZXMsIHIudHJhaW5pbmdfbGFiZWxzLCBlcG9jaHMgPSAyNTAsCiAgICAgICAgICAgICAgICAgICAgdmFsaWRhdGlvbl9kYXRhID0gKHIudmFsaWRhdGlvbl9mZWF0dXJlcywgci52YWxpZGF0aW9uX2xhYmVscyksIHZlcmJvc2UgPSAwKQogICAgICAgICAgICAgICAgICAgIAptb2RlbC5zYXZlKCJweXRob25fbW9kZWwuaDUiKQpgYGAKCiMjIyBncmFwaGlzY2hlIEF1c2dhYmUgZGVyIE1vZGVsbG9wdGltaWVydW5nCgpgYGB7cn0KIyBHcmFwaGlzY2hlIEF1c2dhYmUgZGVyIE1vZGVsbG9wdGltaWVydW5nCgojY3JlYXRlIGRhdGEKZGF0YSA8LSBkYXRhLmZyYW1lKHZhbF9sb3NzID0gdW5saXN0KHB5JGhpc3RvcnkkaGlzdG9yeSR2YWxfbG9zcyksCiAgICAgICAgICAgICAgICAgICBsb3NzID0gdW5saXN0KHB5JGhpc3RvcnkkaGlzdG9yeSRsb3NzKSkKCmdncGxvdChkYXRhWy0oMToxMCksIF0pKwogIGdlb21fbGluZShhZXMoeCA9IDE6bGVuZ3RoKHZhbF9sb3NzKSwgeSA9IHZhbF9sb3NzLCBjb2xvdXIgPSAiVmFsaWRhdGlvbiBMb3NzIikpICsKICBnZW9tX2xpbmUoYWVzKHggPSAxOmxlbmd0aChsb3NzKSwgeSA9IGxvc3MsIGNvbG91ciA9ICJUcmFpbmluZyBMb3NzIikpICsKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGMoIlRyYWluaW5nIExvc3MiPSJibHVlIiwgIlZhbGlkYXRpb24gTG9zcyIgPSAicmVkIikpICsKICBsYWJzKHRpdGxlID0gIkxvc3MgRnVuY3Rpb24gVmFsdWVzIER1cmluZyBPcHRpbWF6YXRpb24iKSArCiAgeGxhYigiSXRlcmF0aW9uIE51bWJlciIpICsKICB5bGFiKCJMb3NzIikKYGBgCgojIyMgQXVzd2VydHVuZyBkZXIgU2Now6R0emVyZ2Vibmlzc2UKCmBgYHtyfQojIFNjaMOkdHp1bmcgZGVyIChub3JtaWVydGVuKSBQcmVpc2UgZsO8ciBkaWUgVHJhaW5pbmdzLSB1bmQgVGVzdGRhdGVuCnRyYWluaW5nX3ByZWRpY3Rpb25zIDwtIHB5JG1vZGVsJHByZWRpY3QodHJhaW5pbmdfZmVhdHVyZXMpCnZhbGlkYXRpb25fcHJlZGljdGlvbnMgPC0gcHkkbW9kZWwkcHJlZGljdCh2YWxpZGF0aW9uX2ZlYXR1cmVzKQp0ZXN0aW5nX3ByZWRpY3Rpb25zIDwtIHB5JG1vZGVsJHByZWRpY3QodGVzdGluZ19mZWF0dXJlcykKCiMgVmVyZ2xlaWNoIGRlciBHw7x0ZWtyaXRlcmllbiBmw7xyIGRpZSBUcmFpbmdpbmdzLSB1bmQgVGVzdGRhdGVuCmEgPC0gZm9ybWF0KG1hcGUodHJhaW5pbmdfbGFiZWxzWywxXSwgdHJhaW5pbmdfcHJlZGljdGlvbnNbLDFdKSoxMDAsIGRpZ2l0cz0zLCBuc21hbGw9MikKYiA8LSBmb3JtYXQobWFwZSh0cmFpbmluZ19sYWJlbHNbLDJdLCB0cmFpbmluZ19wcmVkaWN0aW9uc1ssMl0pKjEwMCwgZGlnaXRzPTMsIG5zbWFsbD0yKQpjIDwtIGZvcm1hdChtYXBlKHRyYWluaW5nX2xhYmVsc1ssM10sIHRyYWluaW5nX3ByZWRpY3Rpb25zWywzXSkqMTAwLCBkaWdpdHM9MywgbnNtYWxsPTIpCmQgPC0gZm9ybWF0KG1hcGUodHJhaW5pbmdfbGFiZWxzWyw0XSwgdHJhaW5pbmdfcHJlZGljdGlvbnNbLDRdKSoxMDAsIGRpZ2l0cz0zLCBuc21hbGw9MikKZSA8LSBmb3JtYXQobWFwZSh0cmFpbmluZ19sYWJlbHNbLDVdLCB0cmFpbmluZ19wcmVkaWN0aW9uc1ssNV0pKjEwMCwgZGlnaXRzPTMsIG5zbWFsbD0yKQoKY2F0KHBhc3RlMCgiXG5NQVBFIG9uIHRoZSBUcmFpbmluZyBEYXRhMTpcdCIsIGEpKQpjYXQocGFzdGUwKCJcbk1BUEUgb24gdGhlIFRyYWluaW5nIERhdGEyOlx0IiwgYikpCmNhdChwYXN0ZTAoIlxuTUFQRSBvbiB0aGUgVHJhaW5pbmcgRGF0YTM6XHQiLCBjKSkKY2F0KHBhc3RlMCgiXG5NQVBFIG9uIHRoZSBUcmFpbmluZyBEYXRhNDpcdCIsIGQpKQpjYXQocGFzdGUwKCJcbk1BUEUgb24gdGhlIFRyYWluaW5nIERhdGE1Olx0IiwgZSwgIlxuIikpCgpnIDwtIGZvcm1hdChtYXBlKHZhbGlkYXRpb25fbGFiZWxzWywxXSwgdmFsaWRhdGlvbl9wcmVkaWN0aW9uc1ssMV0pKjEwMCwgZGlnaXRzPTMsIG5zbWFsbD0yKQpoIDwtIGZvcm1hdChtYXBlKHZhbGlkYXRpb25fbGFiZWxzWywyXSwgdmFsaWRhdGlvbl9wcmVkaWN0aW9uc1ssMl0pKjEwMCwgZGlnaXRzPTMsIG5zbWFsbD0yKQppIDwtIGZvcm1hdChtYXBlKHZhbGlkYXRpb25fbGFiZWxzWywzXSwgdmFsaWRhdGlvbl9wcmVkaWN0aW9uc1ssM10pKjEwMCwgZGlnaXRzPTMsIG5zbWFsbD0yKQpqIDwtIGZvcm1hdChtYXBlKHZhbGlkYXRpb25fbGFiZWxzWyw0XSwgdmFsaWRhdGlvbl9wcmVkaWN0aW9uc1ssNF0pKjEwMCwgZGlnaXRzPTMsIG5zbWFsbD0yKQprIDwtIGZvcm1hdChtYXBlKHZhbGlkYXRpb25fbGFiZWxzWyw1XSwgdmFsaWRhdGlvbl9wcmVkaWN0aW9uc1ssNV0pKjEwMCwgZGlnaXRzPTMsIG5zbWFsbD0yKQogIApjYXQocGFzdGUwKCJcbk1BUEUgb24gdGhlIFZhbGlkYXRpb24gRGF0YTE6XHQiLCBnKSkKY2F0KHBhc3RlMCgiXG5NQVBFIG9uIHRoZSBWYWxpZGF0aW9uIERhdGEyOlx0IiwgaCkpCmNhdChwYXN0ZTAoIlxuTUFQRSBvbiB0aGUgVmFsaWRhdGlvbiBEYXRhMzpcdCIsIGkpKQpjYXQocGFzdGUwKCJcbk1BUEUgb24gdGhlIFZhbGlkYXRpb24gRGF0YTQ6XHQiLCBqKSkKY2F0KHBhc3RlMCgiXG5NQVBFIG9uIHRoZSBWYWxpZGF0aW9uIERhdGE1Olx0IiwgaywgIlxuIikpCgojIE1lYW4gb2YgVHJhaW5pbmcgYW5kIFZhbGlkYXRpb24gRGF0YSBNQVBFCm1lYW5UIDwtIGMoYXMuZG91YmxlKGEpLCBhcy5kb3VibGUoYiksIGFzLmRvdWJsZShjKSwgYXMuZG91YmxlKGQpLCBhcy5kb3VibGUoZSkpIAptZWFuViA8LSBjKGFzLmRvdWJsZShnKSwgYXMuZG91YmxlKGgpLCBhcy5kb3VibGUoaSksIGFzLmRvdWJsZShqKSwgYXMuZG91YmxlKGspKSAKCmNhdChwYXN0ZTAoIlxuTWVhbiBUcmFpbmluZyBNQVBFOiAiLCBtZWFuKG1lYW5UKSwgIlxuIikpCmNhdChwYXN0ZTAoIk1lYW4gVmFsaWRhdGlvbiBNQVBFOiAiLCBtZWFuKG1lYW5WKSwgIlxuIikpCmBgYAoKIyMjIEdyYWZpc2NoZXIgdmVyZ2xlaWNoIGRlciB2b3JoZXJnZXNhZ3RlbiAmIHRhdHPDpGNobGljaGVyIFByZWlzZSBmw7xyIGRpZSBUcmFpbmluZ3MtIHVuZCBWYWxpZGllcnVuZ3NkYXRlbgoKYGBge3J9CmRhdGFfdHJhaW4gPC0gZGF0YS5mcmFtZShwcmVkaWN0aW9uID0gdHJhaW5pbmdfcHJlZGljdGlvbnNbLDFdLCBhY3R1YWwgPSB0cmFpbmluZ19sYWJlbHNbLDFdKQpkYXRhX3ZhbCA8LSBkYXRhLmZyYW1lKHByZWRpY3Rpb24gPSB2YWxpZGF0aW9uX3ByZWRpY3Rpb25zWywxXSwgYWN0dWFsID0gdmFsaWRhdGlvbl9sYWJlbHNbLDFdKQpkYXRhX3Rlc3QgPC0gZGF0YS5mcmFtZShwcmVkaWN0aW9uID0gdGVzdGluZ19wcmVkaWN0aW9uc1ssMV0pCgojIFBsb3QgZGVyIEVyZ2Vibmlzc2UgZGVyIFRyYWluaW5nc2RhdGVuCmdncGxvdChkYXRhX3RyYWluW10pICsKICBnZW9tX2xpbmUoIGFlcyh4PTE6bGVuZ3RoKHByZWRpY3Rpb24pLCB5PXByZWRpY3Rpb24sIGNvbG91ciA9ICJQcmVkaWN0ZWQgVmFsdWVzIiApKSArCiAgZ2VvbV9saW5lKCBhZXMoeD0xOmxlbmd0aChhY3R1YWwpLCB5PWFjdHVhbCwgY29sb3VyID0gIkFjdHVhbCBWYWx1ZXMiICkpICsKICBzY2FsZV9jb2xvdXJfbWFudWFsKCB2YWx1ZXMgPSBjKCJQcmVkaWN0ZWQgVmFsdWVzIj0iYmx1ZSIsICJBY3R1YWwgVmFsdWVzIj0icmVkIikgKSArCiAgbGFicyh0aXRsZT0iUHJlZGljdGVkIGFuZCBBY3R1YWwgVmFsdWVzIGZvciB0aGUgVHJhaW5pbmcgRGF0YSAxIikgKwogIHhsYWIoIkNhc2UgTnVtYmVyIikgKwogIHlsYWIoIlByaWNlIGluIEVVUiIpIAoKIyBQbG90IGRlciBFcmdlYm5pc3NlIGRlciBWYWxpZGllcnVuZ3NkYXRlbgpnZ3Bsb3QoZGF0YV92YWxbLF0pICsKICBnZW9tX2xpbmUoIGFlcyh4PTE6bGVuZ3RoKHByZWRpY3Rpb24pLCB5PXByZWRpY3Rpb24sIGNvbG91ciA9ICJQcmVkaWN0ZWQgVmFsdWVzIiApKSArCiAgZ2VvbV9saW5lKCBhZXMoeD0xOmxlbmd0aChhY3R1YWwpLCB5PWFjdHVhbCwgY29sb3VyID0gIkFjdHVhbCBWYWx1ZXMiICkpICsKICBzY2FsZV9jb2xvdXJfbWFudWFsKCB2YWx1ZXMgPSBjKCJQcmVkaWN0ZWQgVmFsdWVzIj0iYmx1ZSIsICJBY3R1YWwgVmFsdWVzIj0icmVkIikgKSArCiAgbGFicyh0aXRsZT0iUHJlZGljdGVkIGFuZCBBY3R1YWwgVmFsdWVzIGZvciB0aGUgVmFsaWRhdGlvbiBEYXRhIDEiKSArCiAgeGxhYigiQ2FzZSBOdW1iZXIiKSArCiAgeWxhYigiUHJpY2UgaW4gRVVSIikKCiMgUGxvdCBkZXIgRXJnZWJuaXNzZSBkZXIgVGVzdGRhdGVuCmdncGxvdChkYXRhX3Rlc3QpICsKICBnZW9tX2xpbmUoIGFlcyh4PTE6bGVuZ3RoKHByZWRpY3Rpb24pLCB5PXByZWRpY3Rpb24sIGNvbG91ciA9ICJQcmVkaWN0ZWQgVmFsdWVzIiApKSArCiAgbGFicyh0aXRsZT0iUHJlZGljdGlvbiBmb3IgdGhlIFRlc3QgRGF0YSAxIikgKwogIHhsYWIoIkNhc2UgTnVtYmVyIikgKwogIHlsYWIoIlByaWNlIGluIEVVUiIpIAoKIy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gMiAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIwoKZGF0YV90cmFpbjIgPC0gZGF0YS5mcmFtZShwcmVkaWN0aW9uID0gdHJhaW5pbmdfcHJlZGljdGlvbnNbLDJdLCBhY3R1YWwgPSB0cmFpbmluZ19sYWJlbHNbLDJdKQpkYXRhX3ZhbDIgPC0gZGF0YS5mcmFtZShwcmVkaWN0aW9uID0gdmFsaWRhdGlvbl9wcmVkaWN0aW9uc1ssMl0sIGFjdHVhbCA9IHZhbGlkYXRpb25fbGFiZWxzWywyXSkKZGF0YV90ZXN0MiA8LSBkYXRhLmZyYW1lKHByZWRpY3Rpb24gPSB0ZXN0aW5nX3ByZWRpY3Rpb25zWywyXSkKCiMgUGxvdCBkZXIgRXJnZWJuaXNzZSBkZXIgVHJhaW5pbmdzZGF0ZW4KZ2dwbG90KGRhdGFfdHJhaW4yKSArCiAgZ2VvbV9saW5lKCBhZXMoeD0xOmxlbmd0aChwcmVkaWN0aW9uKSwgeT1wcmVkaWN0aW9uLCBjb2xvdXIgPSAiUHJlZGljdGVkIFZhbHVlcyIgKSkgKwogIGdlb21fbGluZSggYWVzKHg9MTpsZW5ndGgoYWN0dWFsKSwgeT1hY3R1YWwsIGNvbG91ciA9ICJBY3R1YWwgVmFsdWVzIiApKSArCiAgc2NhbGVfY29sb3VyX21hbnVhbCggdmFsdWVzID0gYygiUHJlZGljdGVkIFZhbHVlcyI9ImJsdWUiLCAiQWN0dWFsIFZhbHVlcyI9InJlZCIpICkgKwogIGxhYnModGl0bGU9IlByZWRpY3RlZCBhbmQgQWN0dWFsIFZhbHVlcyBmb3IgdGhlIFRyYWluaW5nIERhdGEgMiIpICsKICB4bGFiKCJDYXNlIE51bWJlciIpICsKICB5bGFiKCJQcmljZSBpbiBFVVIiKSAKCiMgUGxvdCBkZXIgRXJnZWJuaXNzZSBkZXIgVmFsaWRpZXJ1bmdzZGF0ZW4KZ2dwbG90KGRhdGFfdmFsMikgKwogIGdlb21fbGluZSggYWVzKHg9MTpsZW5ndGgocHJlZGljdGlvbiksIHk9cHJlZGljdGlvbiwgY29sb3VyID0gIlByZWRpY3RlZCBWYWx1ZXMiICkpICsKICBnZW9tX2xpbmUoIGFlcyh4PTE6bGVuZ3RoKGFjdHVhbCksIHk9YWN0dWFsLCBjb2xvdXIgPSAiQWN0dWFsIFZhbHVlcyIgKSkgKwogIHNjYWxlX2NvbG91cl9tYW51YWwoIHZhbHVlcyA9IGMoIlByZWRpY3RlZCBWYWx1ZXMiPSJibHVlIiwgIkFjdHVhbCBWYWx1ZXMiPSJyZWQiKSApICsKICBsYWJzKHRpdGxlPSJQcmVkaWN0ZWQgYW5kIEFjdHVhbCBWYWx1ZXMgZm9yIHRoZSBWYWxpZGF0aW9uIERhdGEgMiIpICsKICB4bGFiKCJDYXNlIE51bWJlciIpICsKICB5bGFiKCJQcmljZSBpbiBFVVIiKQoKIyBQbG90IGRlciBFcmdlYm5pc3NlIGRlciBUZXN0ZGF0ZW4KZ2dwbG90KGRhdGFfdGVzdDIpICsKICBnZW9tX2xpbmUoIGFlcyh4PTE6bGVuZ3RoKHByZWRpY3Rpb24pLCB5PXByZWRpY3Rpb24sIGNvbG91ciA9ICJQcmVkaWN0ZWQgVmFsdWVzIiApKSArCiAgbGFicyh0aXRsZT0iUHJlZGljdGlvbiBmb3IgdGhlIFRlc3QgRGF0YSAyIikgKwogIHhsYWIoIkNhc2UgTnVtYmVyIikgKwogIHlsYWIoIlByaWNlIGluIEVVUiIpIAoKIy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gMyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIwoKZGF0YV90cmFpbjMgPC0gZGF0YS5mcmFtZShwcmVkaWN0aW9uID0gdHJhaW5pbmdfcHJlZGljdGlvbnNbLDNdLCBhY3R1YWwgPSB0cmFpbmluZ19sYWJlbHNbLDNdKQpkYXRhX3ZhbDMgPC0gZGF0YS5mcmFtZShwcmVkaWN0aW9uID0gdmFsaWRhdGlvbl9wcmVkaWN0aW9uc1ssM10sIGFjdHVhbCA9IHZhbGlkYXRpb25fbGFiZWxzWywzXSkKZGF0YV90ZXN0MyA8LSBkYXRhLmZyYW1lKHByZWRpY3Rpb24gPSB0ZXN0aW5nX3ByZWRpY3Rpb25zWywzXSkKCiMgUGxvdCBkZXIgRXJnZWJuaXNzZSBkZXIgVHJhaW5pbmdzZGF0ZW4KZ2dwbG90KGRhdGFfdHJhaW4zKSArCiAgZ2VvbV9saW5lKCBhZXMoeD0xOmxlbmd0aChwcmVkaWN0aW9uKSwgeT1wcmVkaWN0aW9uLCBjb2xvdXIgPSAiUHJlZGljdGVkIFZhbHVlcyIgKSkgKwogIGdlb21fbGluZSggYWVzKHg9MTpsZW5ndGgoYWN0dWFsKSwgeT1hY3R1YWwsIGNvbG91ciA9ICJBY3R1YWwgVmFsdWVzIiApKSArCiAgc2NhbGVfY29sb3VyX21hbnVhbCggdmFsdWVzID0gYygiUHJlZGljdGVkIFZhbHVlcyI9ImJsdWUiLCAiQWN0dWFsIFZhbHVlcyI9InJlZCIpICkgKwogIGxhYnModGl0bGU9IlByZWRpY3RlZCBhbmQgQWN0dWFsIFZhbHVlcyBmb3IgdGhlIFRyYWluaW5nIERhdGEgMyIpICsKICB4bGFiKCJDYXNlIE51bWJlciIpICsKICB5bGFiKCJQcmljZSBpbiBFVVIiKSAKCiMgUGxvdCBkZXIgRXJnZWJuaXNzZSBkZXIgVmFsaWRpZXJ1bmdzZGF0ZW4KZ2dwbG90KGRhdGFfdmFsMykgKwogIGdlb21fbGluZSggYWVzKHg9MTpsZW5ndGgocHJlZGljdGlvbiksIHk9cHJlZGljdGlvbiwgY29sb3VyID0gIlByZWRpY3RlZCBWYWx1ZXMiICkpICsKICBnZW9tX2xpbmUoIGFlcyh4PTE6bGVuZ3RoKGFjdHVhbCksIHk9YWN0dWFsLCBjb2xvdXIgPSAiQWN0dWFsIFZhbHVlcyIgKSkgKwogIHNjYWxlX2NvbG91cl9tYW51YWwoIHZhbHVlcyA9IGMoIlByZWRpY3RlZCBWYWx1ZXMiPSJibHVlIiwgIkFjdHVhbCBWYWx1ZXMiPSJyZWQiKSApICsKICBsYWJzKHRpdGxlPSJQcmVkaWN0ZWQgYW5kIEFjdHVhbCBWYWx1ZXMgZm9yIHRoZSBWYWxpZGF0aW9uIERhdGEgMyIpICsKICB4bGFiKCJDYXNlIE51bWJlciIpICsKICB5bGFiKCJQcmljZSBpbiBFVVIiKQoKIyBQbG90IGRlciBFcmdlYm5pc3NlIGRlciBUZXN0ZGF0ZW4KZ2dwbG90KGRhdGFfdGVzdDMpICsKICBnZW9tX2xpbmUoIGFlcyh4PTE6bGVuZ3RoKHByZWRpY3Rpb24pLCB5PXByZWRpY3Rpb24sIGNvbG91ciA9ICJQcmVkaWN0ZWQgVmFsdWVzIiApKSArCiAgbGFicyh0aXRsZT0iUHJlZGljdGlvbiBmb3IgdGhlIFRlc3QgRGF0YSAzIikgKwogIHhsYWIoIkNhc2UgTnVtYmVyIikgKwogIHlsYWIoIlByaWNlIGluIEVVUiIpIAoKCiMtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIDQgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSMKCmRhdGFfdHJhaW40IDwtIGRhdGEuZnJhbWUocHJlZGljdGlvbiA9IHRyYWluaW5nX3ByZWRpY3Rpb25zWyw0XSwgYWN0dWFsID0gdHJhaW5pbmdfbGFiZWxzWyw0XSkKZGF0YV92YWw0IDwtIGRhdGEuZnJhbWUocHJlZGljdGlvbiA9IHZhbGlkYXRpb25fcHJlZGljdGlvbnNbLDRdLCBhY3R1YWwgPSB2YWxpZGF0aW9uX2xhYmVsc1ssNF0pCmRhdGFfdGVzdDQgPC0gZGF0YS5mcmFtZShwcmVkaWN0aW9uID0gdGVzdGluZ19wcmVkaWN0aW9uc1ssNF0pCgojIFBsb3QgZGVyIEVyZ2Vibmlzc2UgZGVyIFRyYWluaW5nc2RhdGVuCmdncGxvdChkYXRhX3RyYWluNCkgKwogIGdlb21fbGluZSggYWVzKHg9MTpsZW5ndGgocHJlZGljdGlvbiksIHk9cHJlZGljdGlvbiwgY29sb3VyID0gIlByZWRpY3RlZCBWYWx1ZXMiICkpICsKICBnZW9tX2xpbmUoIGFlcyh4PTE6bGVuZ3RoKGFjdHVhbCksIHk9YWN0dWFsLCBjb2xvdXIgPSAiQWN0dWFsIFZhbHVlcyIgKSkgKwogIHNjYWxlX2NvbG91cl9tYW51YWwoIHZhbHVlcyA9IGMoIlByZWRpY3RlZCBWYWx1ZXMiPSJibHVlIiwgIkFjdHVhbCBWYWx1ZXMiPSJyZWQiKSApICsKICBsYWJzKHRpdGxlPSJQcmVkaWN0ZWQgYW5kIEFjdHVhbCBWYWx1ZXMgZm9yIHRoZSBUcmFpbmluZyBEYXRhIDQiKSArCiAgeGxhYigiQ2FzZSBOdW1iZXIiKSArCiAgeWxhYigiUHJpY2UgaW4gRVVSIikgCgojIFBsb3QgZGVyIEVyZ2Vibmlzc2UgZGVyIFZhbGlkaWVydW5nc2RhdGVuCmdncGxvdChkYXRhX3ZhbDQpICsKICBnZW9tX2xpbmUoIGFlcyh4PTE6bGVuZ3RoKHByZWRpY3Rpb24pLCB5PXByZWRpY3Rpb24sIGNvbG91ciA9ICJQcmVkaWN0ZWQgVmFsdWVzIiApKSArCiAgZ2VvbV9saW5lKCBhZXMoeD0xOmxlbmd0aChhY3R1YWwpLCB5PWFjdHVhbCwgY29sb3VyID0gIkFjdHVhbCBWYWx1ZXMiICkpICsKICBzY2FsZV9jb2xvdXJfbWFudWFsKCB2YWx1ZXMgPSBjKCJQcmVkaWN0ZWQgVmFsdWVzIj0iYmx1ZSIsICJBY3R1YWwgVmFsdWVzIj0icmVkIikgKSArCiAgbGFicyh0aXRsZT0iUHJlZGljdGVkIGFuZCBBY3R1YWwgVmFsdWVzIGZvciB0aGUgVmFsaWRhdGlvbiBEYXRhIDQiKSArCiAgeGxhYigiQ2FzZSBOdW1iZXIiKSArCiAgeWxhYigiUHJpY2UgaW4gRVVSIikKCiMgUGxvdCBkZXIgRXJnZWJuaXNzZSBkZXIgVGVzdGRhdGVuCmdncGxvdChkYXRhX3Rlc3Q0KSArCiAgZ2VvbV9saW5lKCBhZXMoeD0xOmxlbmd0aChwcmVkaWN0aW9uKSwgeT1wcmVkaWN0aW9uLCBjb2xvdXIgPSAiUHJlZGljdGVkIFZhbHVlcyIgKSkgKwogIGxhYnModGl0bGU9IlByZWRpY3Rpb24gZm9yIHRoZSBUZXN0IERhdGEgNCIpICsKICB4bGFiKCJDYXNlIE51bWJlciIpICsKICB5bGFiKCJQcmljZSBpbiBFVVIiKSAKCiMtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIDUgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSMKCmRhdGFfdHJhaW41IDwtIGRhdGEuZnJhbWUocHJlZGljdGlvbiA9IHRyYWluaW5nX3ByZWRpY3Rpb25zWyw1XSwgYWN0dWFsID0gdHJhaW5pbmdfbGFiZWxzWyw1XSkKZGF0YV92YWw1IDwtIGRhdGEuZnJhbWUocHJlZGljdGlvbiA9IHZhbGlkYXRpb25fcHJlZGljdGlvbnNbLDVdLCBhY3R1YWwgPSB2YWxpZGF0aW9uX2xhYmVsc1ssNV0pCmRhdGFfdGVzdDUgPC0gZGF0YS5mcmFtZShwcmVkaWN0aW9uID0gdGVzdGluZ19wcmVkaWN0aW9uc1ssNV0pCgojIFBsb3QgZGVyIEVyZ2Vibmlzc2UgZGVyIFRyYWluaW5nc2RhdGVuCmdncGxvdChkYXRhX3RyYWluNSkgKwogIGdlb21fbGluZSggYWVzKHg9MTpsZW5ndGgocHJlZGljdGlvbiksIHk9cHJlZGljdGlvbiwgY29sb3VyID0gIlByZWRpY3RlZCBWYWx1ZXMiICkpICsKICBnZW9tX2xpbmUoIGFlcyh4PTE6bGVuZ3RoKGFjdHVhbCksIHk9YWN0dWFsLCBjb2xvdXIgPSAiQWN0dWFsIFZhbHVlcyIgKSkgKwogIHNjYWxlX2NvbG91cl9tYW51YWwoIHZhbHVlcyA9IGMoIlByZWRpY3RlZCBWYWx1ZXMiPSJibHVlIiwgIkFjdHVhbCBWYWx1ZXMiPSJyZWQiKSApICsKICBsYWJzKHRpdGxlPSJQcmVkaWN0ZWQgYW5kIEFjdHVhbCBWYWx1ZXMgZm9yIHRoZSBUcmFpbmluZyBEYXRhIDUiKSArCiAgeGxhYigiQ2FzZSBOdW1iZXIiKSArCiAgeWxhYigiUHJpY2UgaW4gRVVSIikgCgojIFBsb3QgZGVyIEVyZ2Vibmlzc2UgZGVyIFZhbGlkaWVydW5nc2RhdGVuCmdncGxvdChkYXRhX3ZhbDUpICsKICBnZW9tX2xpbmUoIGFlcyh4PTE6bGVuZ3RoKHByZWRpY3Rpb24pLCB5PXByZWRpY3Rpb24sIGNvbG91ciA9ICJQcmVkaWN0ZWQgVmFsdWVzIiApKSArCiAgZ2VvbV9saW5lKCBhZXMoeD0xOmxlbmd0aChhY3R1YWwpLCB5PWFjdHVhbCwgY29sb3VyID0gIkFjdHVhbCBWYWx1ZXMiICkpICsKICBzY2FsZV9jb2xvdXJfbWFudWFsKCB2YWx1ZXMgPSBjKCJQcmVkaWN0ZWQgVmFsdWVzIj0iYmx1ZSIsICJBY3R1YWwgVmFsdWVzIj0icmVkIikgKSArCiAgbGFicyh0aXRsZT0iUHJlZGljdGVkIGFuZCBBY3R1YWwgVmFsdWVzIGZvciB0aGUgVmFsaWRhdGlvbiBEYXRhIDUiKSArCiAgeGxhYigiQ2FzZSBOdW1iZXIiKSArCiAgeWxhYigiUHJpY2UgaW4gRVVSIikKCiMgUGxvdCBkZXIgRXJnZWJuaXNzZSBkZXIgVGVzdGRhdGVuCmdncGxvdChkYXRhX3Rlc3Q1KSArCiAgZ2VvbV9saW5lKCBhZXMoeD0xOmxlbmd0aChwcmVkaWN0aW9uKSwgeT1wcmVkaWN0aW9uLCBjb2xvdXIgPSAiUHJlZGljdGVkIFZhbHVlcyIgKSkgKwogIGxhYnModGl0bGU9IlByZWRpY3Rpb24gZm9yIHRoZSBUZXN0IERhdGEgNSIpICsKICB4bGFiKCJDYXNlIE51bWJlciIpICsKICB5bGFiKCJQcmljZSBpbiBFVVIiKSAKYGBg